这段代码是有关引用类型和值类型的问题,同时涉及到 Equals, ReferenceEquals 的问题。这个问题如果大家清楚的话,就不难理解我下面要说的一个事情。
如果大家仔细研究过 Dictionary<TKey, TValue>,就知道 Dictionary 需要一个 KeyValuePair<TKey, TValue> 作为方法 Add 的参数。那问题是:KeyValuePair<TKey, TValue> 是值类型还是引用类型呢?
答案是值类型,这个设计是正确的。
在弄清楚这个问题之前,我们先看下面的代码,请大家给出这段代码在每个 Console.WriteLine() 调用时的输出结果。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Globalization; namespace Demo { class Program { static void Main(string[] args) { KeyValue<string, Person> foo1 = new KeyValue<string, Person> { Key = "Person", Value = new Person { Name = "Mark" } }; KeyValue<string, Person> foo2 = default(KeyValue<string, Person>); Console.WriteLine(foo1); Console.WriteLine(foo2); foo2 = foo1; Console.WriteLine(foo1.Equals(foo2)); Console.WriteLine(object.ReferenceEquals(foo1, foo2)); Person p1 = new Person(); Person p2 = p1; Console.WriteLine(p1.Equals(p2)); Console.WriteLine(object.ReferenceEquals(p1, p2)); foo2.Key = "New Person"; foo2.Value.Name = "Scott"; Console.WriteLine(foo1.Equals(foo2)); Console.WriteLine(object.ReferenceEquals(foo1, foo2)); Console.WriteLine(foo1); Console.WriteLine(foo2); Action<KeyValue<string, Person>> method = item => { item.Key = "New Person 2"; item.Value.Name = "Dixin"; }; method(foo1); Console.WriteLine(foo1); Console.WriteLine(foo2); Console.ReadKey(); } } /// <summary> /// Represents a person. /// </summary> public class Person : ICloneable { /// <summary> /// Gets or sets the name of the <see cref="Person"/> object. /// </summary> /// <value>A <see cref="String"/> value that specifies the name.</value> public string Name { get; set; } /// <summary> /// Creates a new object that is a copy of the current instance. /// </summary> /// <returns> /// A new object that is a copy of this instance. /// </returns> public object Clone() { return new Person { Name = this.Name }; } /// <summary> /// Returns a <see cref="System.String"/> that represents this instance. /// </summary> /// <returns> /// A <see cref="System.String"/> that represents this instance. /// </returns> public override string ToString() { return this.Name.ToString(); } } /// <summary> /// represents a key-value pair that can be set or retrieved. /// </summary> /// <typeparam name="TKey">The type of the key.</typeparam> /// <typeparam name="TValue">The type of the value.</typeparam> struct KeyValue<TKey, TValue> { public TKey Key { get; set; } public TValue Value { get; set; } /// <summary> /// Returns a <see cref="System.String"/> that represents this instance. /// </summary> /// <returns> /// A <see cref="System.String"/> that represents this instance. /// </returns> public override string ToString() { return string.Format(CultureInfo.CurrentCulture, "{0}: {1}", this.Key, this.Value); } } }
大家先讨论,然后我再来分析。
下面的评论转贴自内部邮件。这种值类型设计大部分与其算法一致性有关,我们的观点不一定正确,大家可以看看。和我们一起讨论。
Tom Huang:
Sorry. It’s sure that the code couldn’t work. Dixin said if design the KeyValuePair is class seems better. My code proves that if it is class, it will cause the key traffic. It’s all based on assumptions
If KeyValuePair is R type. Class KeyValuePair{};
KeyValuePair pair1 = new (“Dixin”, “Male”); KeyValuePair pair2 = new (“Lingling”, “Female”);
Dictionary d = new Dictionary(…..); D.Add(pair1); D.Add(pair2);
// Change the pair1.Key same to pair2 pair1.Key = “Lingling”; // pair2.Key = “Lingling”, it’s will crash with the key in Dictionary. |
Look you code and got your meaning. I think the main idea it same with me that the TKey traffic.
And I also have some reasons for performance and security about that( We can talk later).
Thanks,
Tom.
Me:
The code you provided cannot work. Because KeyValuePair<TKey, TValue> is an immutable object. Once you initialize the instance of this struct, you cannot change its properties.
The reason I can guess why value type is, once the Dictionary is of key int, and value int, take a look at the following code:
Dictionary<int, int> d = new Dictionary<int, int>();
KeyValuePair<int, int> value = new KeyValuePair<int, int>(1, 1);
d[2] = 2;
d.Add(value);
then if I want to get the value of the key “1”, we assume KeyValuePair<TKey, TValue> is a reference type, then we should get the instance of this class we added to the dictionary, that is value. So how it is going if we clone this instance and re-add it to the dictionary? It would work because though the key and the value is the same, the reference is different, the dictionary object may treat the two instances of KeyValuePair as different ones. Thus the hash algorit6hm for this dictionary is not consist. see following code:
KeyValuePair<int, int> value2 = new KeyValuePair<int, int>(value.Key, value.Value);
d.Add(value2); // How it will be?
So to make it consist, only mechanism is define the KeyValuePair<TKey, TValue> as a value type, so that forces the dictionary hashes the value of the key, not the reference objects of the instances in the dictionary.
Thanks,
Mark.
Tom Huang:
1. Thanks for your description about the method where it is put.
2. Seen your article in your blog.
a) The blog told us some different between R type and V type. But I couldn’t figure out what’s the matter with why dictionary use struct but class.
b) I looked in each Dictionary Methods, also haven’t found any important something related to your blog.
I have some very simple reasons for why there use V type but R type.
The most simple one is:
Class KeyValuePair{};
KeyValuePair pair1 = new (“Dixin”, “Male”);
KeyValuePair pair2 = new (“Lingling”, “Female”);
Dictionary d = new Dictionary(…..);
D.Add(pair1);
D.Add(pair2);
// Change the pair1.Key same to pair2
pair1.Key = “Lingling”; // pair2.Key = “Lingling”, it’s will crash with the key in Dictionary.
You must want to say the implement of the method D.Add(…) is not as you think. Please tell me what you think of the implement of the method D.Add(…). Then I think you will find the struct is better in there at last as you think the implement of the method.
Thanks,
Tom.