• 技术文章 >后端开发 >C#.Net教程

    .NET框架-引用陷阱的代码实例分享

    黄舟黄舟2017-03-18 13:39:48原创913
    1 值相等,对象便默认相等?
    .net 容器中判断某个引用类型存在的默认规则是什么? 判断指针值是否相等。

            private static List<int> list;        
            static void Main(string[] args)
            {            
            //新建实例instance1
                MyObject instance1 = new MyObject();
                instance1.Value = 10;            
                //新建list
                List<MyObject> list = new List<MyObject>();            
                //引用实例instance1
                list.Add(instance1);            
                //新建实例:instance2
                MyObject instance2 = new MyObject();            
                //赋值为instance1.Value
                instance2.Value = instance1.Value;       
            }
        }

      用到的Model类:

                public class MyObject
                {
                    public int Value { get; set; }
                }

    下面做1个测试:

                //即便Value相等,instance2与instance1的内存地址不相等!
                bool isExistence1 = list.Contains(instance2);            //isExistence1 : false;

      这个测试结果是false,因为它们指向不同的内存地址,尽管值相等,这便是“值相等,对象不相等”的情况。
      
      引用类型若是想根据其中的某个属性值判断是否相等,那么需要实现IEquatable接口
    若想继续看 根据值是否相等 判断对象是否相等,请参考文章:C# 容器,接口类,性能

    2 引用陷阱?

      一个对象引用另一个对象,某一个改变,另一个便改变。例如,合并两个字典,合并结果是对的,但是却意外改变了原对象。

    在这里举一个例子:

                var dict1 = new Dictionary<string, List<string>>();
                dict1.Add("qaz",new List<string>(){"100"});//含有qaz键
                dict1.Add("wsx",new List<string>(){"13"});            var dict2 = new Dictionary<string, List<string>>();
                dict2.Add("qaz", new List<string>() { "11" });//也含有qaz键
                dict2.Add("edc", new List<string>() { "17" });            //合并2个字典到dict            
                var dictCombine = new Dictionary<string, List<string>>();
                foreach (var ele in dict1) //拿到dict1
                {
                   dictCombine .Add(ele.Key,ele.Value); 
                }
    
                foreach (var ele in dict2) //拿到dict2
                {                if(dictCombine.ContainsKey(ele.Key))//检查重复
                       dictCombine [ele.Key].AddRange(ele.Value); 
                    else
                    {
                        dictCombine .Add(ele.Key,ele.Value); 
                    }
                }

      dictCombine的结果正确,{“qaz”, “100”和”11”}, {“wsx”,”13”},{“edc”,”17”}
    但是dict1的结果怎么样? 被改变了! dict1意外变为了 {“qaz”, “100”和”11”}, {“wsx”,”13”}。 正确的合并,不应该改变dict1!

    分析原因

      dictCombine首先添加了dict1的键值,也就是dictCombine的键值都引用了dict1的键值; 接下来,再合并dict2时,首先判断dictCombine中是否包含了dict2的键,如果包含,则再往dictCombine的键值中添加, 值又引用了同一个对象,也就是在dict1的键中添加了这个值。dictCombine[ele.Key]和dict1[ele.Key]引用是否相等的验证:

    bool flag = object.ReferenceEquals(dictCombine[ele.Key], dict1[ele.Key]);//true

    正解

      避免dictCombine[ele.Key]和dict1[ele.Key]引用相等!!!

    Dictionary<string, List<string>> dict = new Dictionary<string, List<string>>();            
    //先把键都合并到dictCombine中,值都是新创建的
                foreach (var key in dict1.Keys)
                {                if (!dictCombine.ContainsKey(key))
                        dictCombine.Add(key, new List<string>());
                }            foreach (var key in dict2.Keys)
                {                if (!dictCombine.ContainsKey(key))
                        dictCombine.Add(key, new List<string>());
                }     //分别将值添加进去
                foreach (var ele in dict1)
                {
                    dictCombine[ele.Key].AddRange(ele.Value);
                }            foreach (var ele in dict2)
                {
                    dictCombine[ele.Key].AddRange(ele.Value);
                }

    dictCombine合并结果是正确的,并且dict1,dict2都未改变!

    总结
       利用引用相等,带来了很多好处,比如函数间的引用传值(by reference)。但是,如果运用不当,也会给我们带来一些不必要的麻烦。  

    3 引用不当破坏封装?
      
      如果将封装的类内私有字段作为接口方法的返回值,这种做法会破坏类的封装,是特别容易忽视的一个问题。如果忽视这个问题,可能会出现莫名其妙的问题。
      
      如下面的代码所示,
      

    public class TestPrivateEncapsulate
    {
        private List<object> _refObjs;
    
        public List<object> GetRefObjs()
        {
            _refObjs = new List<object>();        ...
            ...
           //其他逻辑处理计算出来的_refObjs={1,4,2};    
            return _refObjs; //返回私有字段
        }
    
        public object GetSumByIterRefObjs()
        {        if (_refObjs == null)            return null;
            foreach (var item in _refObjs)
            {            ...//处理逻辑
            }
        }  
    }

      现在使用刚才写的类TestPrivateEncapsulate,我们先创建一个实例,

    TestPrivateEncapsulate test = new TestPrivateEncapsulate();

      然后调用:

    List<object> wantedObjs = test.GetRefObjs();

      返回的预期wantedObjs应该有3个整形类型的元素,1,4,2。

      继续:

    List<object> sol = wantedObjs; //我们将sol指向wantedObjssol.Add(5); //加入元素5

      等我们想回过头来计算,原来wantedObjs的元素求和:

    test.GetSum();

      我们意外得到了12,而不是预想中的7。这是为什么呢?

      仔细分析后发现,我们在客户端调用,sol.Add(5)后,间接的修改了TestPrivateEncapsulate内变量:_refObjs,它由{1,4,2}修改为了{1,4,2,5}。

      私有变量在客户端被修改了!这便是接口返回私有变量带来的副作用!

      正解:

        // 将原来的公有变为私有
        private List<object> getRefObjs()
        {
            _refObjs = new List<object>();        ...
            ...
           //其他逻辑处理计算出来的_refObjs={1,4,2};    
            return _refObjs; //返回私有字段
        }
    
        //只带只读的属性
        public RefObjs
        {
            get
             {
                getRefObjs();            
                return _refObjs;
             }
        }

      设置一个公有字段,仅带有只读属性,将原来的公有方法GetRefObjs变为私有方法getRefObjs,这样在客户端是不可能修改私有字段的!

    总结
    对象的属性值都等,但对象引用不一定相等;
    两个或多个对象都引用某个对象,若这个对象被修改,则所有引用者属性值也被修改;
    成员返回封装的引用变量,会破坏封装。

    php入门到就业线上直播课:查看学习

    以上就是.NET框架-引用陷阱的代码实例分享的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。

    前端(VUE)零基础到就业课程:点击学习

    清晰的学习路线+老师随时辅导答疑

    自己动手写 PHP MVC 框架:点击学习

    快速了解MVC架构、了解框架底层运行原理

    上一篇:.NET框架-string是value还是reference type的详解 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • ❤️‍🔥共22门课程,总价3725元,会员免费学• ❤️‍🔥接口自动化测试不想写代码?• c语言中关键字有多少个• 解决asp.net中“从客户端中检测到有潜在危险的Request.Form值”的错误• asp.net 图片验证码的HtmlHelper• ASP.NET使用Ajax如何返回Json对象的方法具体介绍• 应用绝对路径与相对路径
    1/1

    PHP中文网