ホームページ  >  記事  >  バックエンド開発  >  C#reference に関する 3 つの意見 (詳細)

C#reference に関する 3 つの意見 (詳細)

黄舟
黄舟オリジナル
2017-03-04 10:53:591735ブラウズ

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;       
        }
    }

使用するモデルクラス:

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

以下でテストをしてみましょう:

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

このテストの結果はfalseです。値は同じですが、異なるメモリアドレスを指しているため、これはです「値は等しい、オブジェクトは等しくない」 状況。

属性値の 1 つに基づいて参照型が等しいかどうかを判断したい場合は、IEquatable インターフェイスを実装する必要があります。
値が等しいかどうかに基づいてオブジェクトが等しいかどうかを引き続き確認したい場合は、次の記事を参照してください: C# コンテナー、インターフェイス クラス、パフォーマンス

2 参照トラップ?

あるオブジェクトが別のオブジェクトを参照する場合、一方が変化すると、他方も変化します。たとえば、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" and "11"}, {"wsx","13"}, {"edc"," 17 ”}
しかし、dict1 の結果はどうでしょうか? 変わってしまった! dict1 は予期せず {"qaz", "100" and "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 も変更されていません。

概要
参照等価性を使用すると、関数間の参照から値への(参照による)など、多くの利点が得られます。しかし、使い方を誤ると無用なトラブルを招くことにもなります。

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つあるはずです。 、4、2。

続き:

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

戻って計算したいとき、wantedObjsの元の要素の合計:

test.GetSum();

期待した7ではなく、誤って12を取得してしまいました。どうしてこれなの?

注意深く分析した結果、クライアントで 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に変更して、クライアント側でプライベートフィールドを変更できないようにします。

概要
オブジェクトの属性値はすべて等しいですが、オブジェクト参照は必ずしも等しいわけではありません。このオブジェクトが変更されると、すべてのリファラーの属性値も変更されます。 ;
メンバーはカプセル化された参照変数を返し、カプセル化を破棄します。

上記は C#reference の 3 つの考え方の詳細な紹介です。その他の関連コンテンツについては、PHP 中国語 Web サイト (www.php.cn) に注目してください。



声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。