.NET Framework - 参照トラップのコード例の共有

黄舟
リリース: 2017-03-18 13:39:48
オリジナル
1462 人が閲覧しました

1 の値が等しい場合、オブジェクトもデフォルトで等しくなりますか?
.net コンテナ内の特定の referencetype の存在を判断するためのデフォルトのルールは何ですか? ポインタ値が等しいかどうかを判断します。

        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; }
            }
ログイン後にコピー

以下のテストを行ってみましょう:

            //即便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に変更して、クライアント側でプライベートフィールドを変更できないようにします。

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

以上が.NET Framework - 参照トラップのコード例の共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!