不得不先说明一下,这又是一篇跟值类型的装箱拆箱有关的文章,尽管我之前已近写了两篇随笔来阐述这个很基础的问题了。它们分别在:这里和这里。本文中的代码示例出自后者,稍作了修改。
我们知道C#是一门“安全”的的语言,以至于它不让我们修改已装箱值类型实例中的字段。因为这种尝试会带来出乎意料的效果。下面就来解释一下为什么会有这种让很多程序员“意外”的情况发生以及如何“欺骗”C#来实现程序员真正的意图,尽管那样做不是合理的方式。
首先还是把我以前在这里提到的那段老代码翻出来:

 1    /**//// <summary>
 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]也来谈谈接口欺诈相关推荐

  1. 《博客园精华集---CLR/C#分册》

    <博客园精华集---CLR/C#分册> 转:http://www.cnblogs.com/anytao/archive/2008/09/04/lovechina_bestclr_3rdfi ...

  2. C#.Net分类随笔列表

    也说C#中的Immutable fox23 2008-07-26 18:29 阅读:646 评论:8   谁动了我的构造函数? AndyHai 2008-07-26 16:18 阅读:643 评论:6 ...

  3. 反欺诈中所用到的机器学习模型有哪些?

    作者 | 微调(知乎ID微调,普华永道高级数据科学家) 反欺诈方向的实际应用很多,我有做过保险业反欺诈和零售快消业的欺诈检测,抛砖引玉的谈谈反欺诈项目的"道"和"术&qu ...

  4. java 接口 实现和继承关系

    一.抽象类 有时候,我们可能想要构造一个很抽象的父类对象,它可能仅仅代表一个分类或抽象概念,它的实例没有任何意义,因此不希望它能被实例化.例如:有一个父类"水果(Fruit)",它 ...

  5. 【待继续研究】如何运用机器学习技术构建可行的反欺诈检测方案?

    反欺诈方向的实际应用很多,我有做过保险业反欺诈和零售快消业的欺诈检测,抛砖引玉的谈谈反欺诈项目的"道"和"术". 1.背景 - 为什么反欺诈检测难度很高? 反欺 ...

  6. ARTS-11(动态规划、线程池解析、Feign原生接口调用、好用工具推荐)

    Algorithm 动态规划思路及解题 Review 线程池的使用 1).多线程的好处 提升资源利用率 提高程序处理效率:例如对执行顺序不敏感的任务,可以交由多个线程进行并行处理 减少了创建和销毁线程 ...

  7. 如何设计出高可用、高性能的接口

    转载于:https://blog.csdn.net/gitchat/article/details/78705978 发起这个 Chat 只是一时兴起,想了一些点就写出来了,但自己一读,感觉一点干货都 ...

  8. Spring常用的拓展接口分门别类

    Spring框架是一个拓展性很好的框架,在平时的开发中我们也会进行一些拓展.那么来看一下常用的拓展类: ​ 编辑切换为居中 添加图片注释,不超过 140 字(可选) 这里把拓展接口分成了四大类 1. ...

  9. Spring 常用的拓展接口分门别类

    Spring 框架是一个拓展性很好的框架,在平时的开发中我们也会进行一些拓展.那么来看一下常用的拓展类: ​ 编辑切换为居中 添加图片注释,不超过 140 字(可选) 这里把拓展接口分成了四大类 1. ...

  10. 常用的一些拓展:Spring拓展接口分门别类

    Spring框架是一个拓展性很好的框架,在平时的开发中我们也会进行一些拓展.那么来看一下常用的拓展类: ​ 编辑切换为居中 添加图片注释,不超过 140 字(可选) 这里把拓展接口分成了四大类 1. ...

最新文章

  1. 终于,我读懂了所有Java集合——map篇
  2. 关于Python、Anaconda、Jupyter
  3. 017-Centos7.6+CDH 6.2 安装和使用
  4. 可视化管理一目了然 锐捷RG-UAC承载荔湾教育局“御网”之道
  5. font-family 各字体一览表
  6. LoadRunner11下载以及详细破解说明
  7. 信息学奥赛一本通(C++版)在线评测系统 基础(一) 第一章 参考答案(AC代码)
  8. Racket编程指南——17 创造语言
  9. 毕业论文引言 文献综述 摘要有什么区别?
  10. CVPR2020论文列表(中英对照)
  11. FZU 2213 Common Tangents(公切线)
  12. 查看网段内正在使用的IP以及ip定位 ——CMD批处理循环
  13. 关注电动汽车能效水平 提高续航能力
  14. C++ 获取特定进程的CPU使用率转
  15. 2022-08-02:小红拿到了一个大立方体,该大立方体由1*1*1的小方块拼成,初始每个小方块都是白色。 小红可以每次选择一个小方块染成红色, 每次小红可能选择同一个小方块重复染色, 每次染色以后,
  16. 微信客服消息时间限制
  17. 深入理解JVM之四:详解垃圾收集器
  18. Ubuntu 根分区扩容方法
  19. ElasticSearch DSL语言高级查询+SpringBoot
  20. 3D动画概述暨骨骼动画实现

热门文章

  1. 泛型学习第一天:List与IList的区别 (二)
  2. Android 关于图片文件夹后缀错误,使应用在源码下编译通过却无法运行的错误...
  3. 何凯明 Single Image Haze Removal Using Dark Channel Prior
  4. 6步学会VS封装DLL
  5. 剑指offer(28)—数组中出现次数超过一半的数字
  6. CSS学习总结(4)——盒模型/背景属性
  7. ArcGIS 可视性分析
  8. ENVI 5.6/IDL 8.8 新特性介绍
  9. 实习成长之路:Redis为什么快?为什么Redis同样也是String字符串,但是要比Java性能好?SDS数据结构是什么?什么是紧凑型编程技巧?
  10. Android 属性动画简单分析(一)