[C#.Tips]也来谈谈接口欺诈
我们知道C#是一门“安全”的的语言,以至于它不让我们修改已装箱值类型实例中的字段。因为这种尝试会带来出乎意料的效果。下面就来解释一下为什么会有这种让很多程序员“意外”的情况发生以及如何“欺骗”C#来实现程序员真正的意图,尽管那样做不是合理的方式。
首先还是把我以前在这里提到的那段老代码翻出来:
2 /// 重新订票的接口
3 /// </summary>
4 internal interface IReBook
5 {
6 Ticket ReBook(String newTerminal, Int32 newDistance);
7 }
8
9 internal struct Ticket:IReBook
10 {
11 private String _start, _terminal;//起点和终点
12 private Int32 _distance;//距离
13
14 public Ticket(string start, string terminal, Int32 distance)
15 {
16 _start = start;
17 _terminal = terminal;
18 _distance = distance;
19 }
20
21
22 /**//// <summary>
23 /// 重写System.ValueType的ToString方法
24 /// </summary>
25 public override String ToString()
26 {
27 return String.Format("From {0} To {1} , {2} km",
28 _start,
29 _terminal,
30 _distance);//在方法的内部,_distance被装箱
31 }
32
33 IReBook Members#region IReBook Members
34 /**//// <summary>
35 /// 重新订票
36 /// </summary>
37 /// <param name="newTerminal">新的终点站</param>
38 /// <param name="newDistance">到终点站的距离</param>
39 public Ticket ReBook(string newTerminal, int newDistance)
40 {
41 _terminal = newTerminal;
42 _distance = newDistance;
43 return this;
44 }
45
46 #endregion
47 }
48
49 public sealed class Program
50 {
51 public static void Main()
52 {
53 Ticket t = new Ticket("北京", "汉口", 1225);
54 //值类型实例t在这里第一次被装箱:Ticket-->Object-->override ToString
55 Console.WriteLine(t);
56
57 //显示的装箱
58 // Console.WriteLine(((Object)t).ToString());
59
60 t.ReBook("上海", 1400);
61 Console.WriteLine(t);
62
63 Object o = t;
64 Console.WriteLine(o);
65
66 Ticket t2 = ((Ticket)o).ReBook("广州", 2000);
67 Console.WriteLine(o);
68 Console.WriteLine(t2);
69
70 //t-->IReBook,被装箱
71 ((IReBook)t).ReBook("广州", 2000);
72 Console.WriteLine(t);
73
74 //o-->IReBook,无须装箱
75 ((IReBook)o).ReBook("广州", 2000);
76 Console.WriteLine(o);
77 }
78 }
跟之前的代码相比,仅仅多了一个接口IReBook,然后在Ticket中实现了这个接口。输出方面,前5个输出都跟原来的代码一样,显示的结果也一样。我在后面增加了两个输出,您可以先猜猜第72行的输出结果会是怎样?
似乎我们原本的意图是修改车票为到广州,2000km。但是这里的输出却仍然是"From 北京 To 上海,1400 km"。这是违背了我们的初衷的(其实原本定义这样的方法就是不合理的)。
是什么原因会让我们的修改“失败”了呢?看了这篇文章的朋友应该能看出来,因为值类型实例t装箱成为引用类型IReBook,我们调用ReBook时,只是在CLR生成的已装箱的值类型实例(姑且称做tII)上进行了修改,由于没有任何引用指向tII,tII会被GC探知并回收。
那么如何强行让这种对值类型实例字段的改变变得合理呢?这就是引入接口的原因,来看第75行,我们把引用类型o(其指向的是已装箱的值类型tII)转型为IReBook,这是两个引用类型之间的转换,不存在装箱拆箱,不创建额外的副本,所以当我们在IReBook上调用ReBook方法时,会理所当然的显示改变后的结果"From 北京 To 广州,2000 km"。
这就是所谓的接口欺诈,间接地修改已装箱值类型的实例字段。
程序完整的输出结果如下:
显然,一个可变(mutable)的值类型,如这里的Ticket ,一般来讲都是不合理的设计,因为这会给我们带来像上述的出乎预料的结果,而且会产生额外的“垃圾”,这在我们定义任何一个值类型的时候都是应当注意的。就这个例子来说,明显定义Ticket为一个class是较好的设计,因为这样避免了产生"失控的对象"以及对它的操作。
总之,"一个值类型成员不应该修改任何实例字段" --《CLR via C#》。
顺便再多说一句关于接口的,一般来说不要尝试把未装箱的值类型转化为接口类型,因为那样做实际上是让CLR在背后为你“悄悄地”创建一个已装箱的值类型,而你却无法控制。
就写到这吧,我一直希望用最简单的话把问题说清楚,同时欢迎大家批评指正 :-)
[C#.Tips]也来谈谈接口欺诈相关推荐
- 《博客园精华集---CLR/C#分册》
<博客园精华集---CLR/C#分册> 转:http://www.cnblogs.com/anytao/archive/2008/09/04/lovechina_bestclr_3rdfi ...
- C#.Net分类随笔列表
也说C#中的Immutable fox23 2008-07-26 18:29 阅读:646 评论:8 谁动了我的构造函数? AndyHai 2008-07-26 16:18 阅读:643 评论:6 ...
- 反欺诈中所用到的机器学习模型有哪些?
作者 | 微调(知乎ID微调,普华永道高级数据科学家) 反欺诈方向的实际应用很多,我有做过保险业反欺诈和零售快消业的欺诈检测,抛砖引玉的谈谈反欺诈项目的"道"和"术&qu ...
- java 接口 实现和继承关系
一.抽象类 有时候,我们可能想要构造一个很抽象的父类对象,它可能仅仅代表一个分类或抽象概念,它的实例没有任何意义,因此不希望它能被实例化.例如:有一个父类"水果(Fruit)",它 ...
- 【待继续研究】如何运用机器学习技术构建可行的反欺诈检测方案?
反欺诈方向的实际应用很多,我有做过保险业反欺诈和零售快消业的欺诈检测,抛砖引玉的谈谈反欺诈项目的"道"和"术". 1.背景 - 为什么反欺诈检测难度很高? 反欺 ...
- ARTS-11(动态规划、线程池解析、Feign原生接口调用、好用工具推荐)
Algorithm 动态规划思路及解题 Review 线程池的使用 1).多线程的好处 提升资源利用率 提高程序处理效率:例如对执行顺序不敏感的任务,可以交由多个线程进行并行处理 减少了创建和销毁线程 ...
- 如何设计出高可用、高性能的接口
转载于:https://blog.csdn.net/gitchat/article/details/78705978 发起这个 Chat 只是一时兴起,想了一些点就写出来了,但自己一读,感觉一点干货都 ...
- Spring常用的拓展接口分门别类
Spring框架是一个拓展性很好的框架,在平时的开发中我们也会进行一些拓展.那么来看一下常用的拓展类: 编辑切换为居中 添加图片注释,不超过 140 字(可选) 这里把拓展接口分成了四大类 1. ...
- Spring 常用的拓展接口分门别类
Spring 框架是一个拓展性很好的框架,在平时的开发中我们也会进行一些拓展.那么来看一下常用的拓展类: 编辑切换为居中 添加图片注释,不超过 140 字(可选) 这里把拓展接口分成了四大类 1. ...
- 常用的一些拓展:Spring拓展接口分门别类
Spring框架是一个拓展性很好的框架,在平时的开发中我们也会进行一些拓展.那么来看一下常用的拓展类: 编辑切换为居中 添加图片注释,不超过 140 字(可选) 这里把拓展接口分成了四大类 1. ...
最新文章
- 终于,我读懂了所有Java集合——map篇
- 关于Python、Anaconda、Jupyter
- 017-Centos7.6+CDH 6.2 安装和使用
- 可视化管理一目了然 锐捷RG-UAC承载荔湾教育局“御网”之道
- font-family 各字体一览表
- LoadRunner11下载以及详细破解说明
- 信息学奥赛一本通(C++版)在线评测系统 基础(一) 第一章 参考答案(AC代码)
- Racket编程指南——17 创造语言
- 毕业论文引言 文献综述 摘要有什么区别?
- CVPR2020论文列表(中英对照)
- FZU 2213 Common Tangents(公切线)
- 查看网段内正在使用的IP以及ip定位 ——CMD批处理循环
- 关注电动汽车能效水平 提高续航能力
- C++ 获取特定进程的CPU使用率转
- 2022-08-02:小红拿到了一个大立方体,该大立方体由1*1*1的小方块拼成,初始每个小方块都是白色。 小红可以每次选择一个小方块染成红色, 每次小红可能选择同一个小方块重复染色, 每次染色以后,
- 微信客服消息时间限制
- 深入理解JVM之四:详解垃圾收集器
- Ubuntu 根分区扩容方法
- ElasticSearch DSL语言高级查询+SpringBoot
- 3D动画概述暨骨骼动画实现
热门文章
- 泛型学习第一天:List与IList的区别 (二)
- Android 关于图片文件夹后缀错误,使应用在源码下编译通过却无法运行的错误...
- 何凯明 Single Image Haze Removal Using Dark Channel Prior
- 6步学会VS封装DLL
- 剑指offer(28)—数组中出现次数超过一半的数字
- CSS学习总结(4)——盒模型/背景属性
- ArcGIS 可视性分析
- ENVI 5.6/IDL 8.8 新特性介绍
- 实习成长之路:Redis为什么快?为什么Redis同样也是String字符串,但是要比Java性能好?SDS数据结构是什么?什么是紧凑型编程技巧?
- Android 属性动画简单分析(一)