早上好,下午或晚上,
仍然基于我在.NET中关于immutable dictionaries的问题,我提出了以下问题:如果TKey
和TValue
是值类型,则可以使字典真正不可变,在某种意义上,它的内部结构和值本身都不能改变,如果这些参数是引用类型键,并且值可以很容易地改变,从而改变字典本身。我是对的吗?
非常感谢。
答案 0 :(得分:23)
如果然后TKey和TValue是值类型,你可以使字典真正不可变,在某种意义上,它的内部结构和值本身都不能改变,如果这些参数是引用类型键和值可以很容易地改变,从而改变字典本身。我是对的吗?
不完全是。你已经确定了一个真正的问题,但你对它的描述还没有完全考虑过。
首先,我注意到,即使它是一个可变值类型,卡在不可变字典中的值类型也是不可变的。为什么?因为值类型只是通过改变包含它们的变量来改变。如果你的字典没有公开它用来存储值类型的变量,那么这些变量就无法改变。
但是,即使值类型本身是不可变的,不可变值类型也可以包含对可变引用类型的引用,现在我们遇到了同样的问题。将字典限制为值类型只会将问题推到某个级别,它无法解决问题!您真正需要保证“深层”不变性的是 blittable 值类型的字典。 “blittable”是指没有引用类型字段的值类型。 (之所以这么称呼是因为你可以通过将这些位“直接”输出到磁盘来将其中一个序列化到存储中。)
不幸的是,泛型类型系统中没有约束来约束blittable值类型。
现在让我们考虑不可变字典中可变引用类型的一般问题,无论这些引用类型是直接存在还是通过值类型的字段存在。如果引用类型在不可变字典中的带外变异会出现什么问题呢?
嗯,首先想到的是,一个不可变的字典应该两次给同一个问题两次相同的答案,现在不再是这种情况了。如果您说“customers [name] .Address”,您希望两次获得相同的答案,但如果客户是可以变异的引用类型,您将得到一个可能不同的答案。这可能是也可能不是可取的。 (并注意字典两次给出相同的答案:它给客户对象提供相同的引用。实际上客户对象没有给出相同的答案两次。)
假设您没有尝试回忆可能会改变的问题的答案,这通常不是一个大问题。
更大的问题是当哈希表中的对象作为键变异时,从而改变其哈希值并“丢失”表中的对象。
如果发生这种情况,那么有人不会遵守指南。指南是(1)引用类型应尽可能不将其哈希码(和相等)基于可变异的数据,以及(2)不应对作为哈希表中的键使用的对象进行变异。
答案 1 :(得分:2)
结构是值类型,不一定是不可变的 - 所以答案是否定的。您可以设计不可变类型(只读取所有字段和属性)。但是,没有可用的类型约束(如where TKey : class
),这将允许您强制执行它。
更新:示例:
class Bar { public int I; }
struct Foo { public Bar B; }
var b = new Bar();
var f = new Foo { B = b; }
dict[key] = f;
b.I++;
我承认有点构建。
答案 2 :(得分:2)
看看新的BCL不可变集合: http://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx
这仍是预览版,但它包含以下类型:
ImmutableStack<T>
ImmutableQueue<T>
ImmutableList<T>
ImmutableHashSet<T>
ImmutableSortedSet<T>
ImmutableDictionary<K, V>
ImmutableSortedDictionary<K, V>
我希望下一版本的C#将包含用于在类级别指定不变性约束的关键字(类似于建议的here),以及更轻松地克隆对象的方法,例如what can be done in F#关键字with
:
var o1 = new CustomObject { Field1 = 0, Field2 = 3 }
var o2 = o1 with { Field1 = 1}
答案 3 :(得分:1)
这是一个非常快速的设计,可能不会直接编译,但希望会给你一些想法......
[Immutable]
class ImmutableDictionary<TKey, TValue> : Dictionary<TKey, TValue>
{
public ImmutableDictionary(IEnumerable<KeyValuePair<TKey, TValue>> keysValues)
{
// Ensure TKey is immutable...
if (typeof(TKey).GetCustomAttribute(typeof(ImmutableAttribute), false).Length == 0)
throw new InvalidOperationException(String.Format("Type '{0}' must be immutable.", typeof(TKey).AssemblyQualifiedName);
// Ensure TValue is immutable...
if (typeof(TValue).GetCustomAttribute(typeof(ImmutableAttribute), false).Length == 0)
throw new InvalidOperationException(String.Format("Type '{0}' must be immutable.", typeof(TValue).AssemblyQualifiedName);
foreach(var keyValue in keysValues)
base.Add(keyValue.Key, keyValue.Value);
}
public new void Add(TKey key, TValue value)
{
throw new InvalidOperationException("Cannot modify contents of immutable dictionary.");
}
public new void Clear()
{
throw new InvalidOperationException("Cannot modify contents of immutable dictionary.");
}
public new void Remove(TKey key)
{
throw new InvalidOperationException("Cannot modify contents of immutable dictionary.");
}
public TValue this[TKey key]
{
get { return base[key]; }
set
{
throw new InvalidOperationException("Cannot modify contents of immutable dictionary.");
}
}
}
答案 4 :(得分:0)
要了解发生了什么,请下拉到比字典更简单的类型:List<T>
。如果创建一个List<T>
,则抛出一些项目,然后创建将其传递给ReadOnlyCollection<T>
的构造函数,并销毁除ReadOnlyCollection<T>
中包含的所有对该列表的引用,然后该列表将是不可变的。无论T
是可变的还是不可变的,类或结构,它的状态永远不会改变。如果T
是一个可变类类型,那么将这样的列表视为可变的任何人都会错误地将该列表视为保持对象。它没有。
如果T
是类类型,List<T>
不持有对象 - 标识它们。如果List<int[]>
如上所述变为不可变,则在添加了五个int
数组引用之后,只要它存在,它将始终保持对这五个数组的引用。数组的内容可能会更改,但 List<T>
引用的对象的属性不构成列表状态的一部分。
Dictionary<TKey,TValue>
的状态比List<T>
的状态更难定义,特别是如果考虑到由于GetHashCode()
实现引起的混乱状态的可能性,同样的原则适用。如果TKey
是类类型,则构成字典状态一部分的TKey
实例的唯一方面是GetHashCode
返回的值,以及{{1}隐含的等价类}} 方法;这两件事都应该是不可改变的。 Equals
或TKey
类对象的合法可变方面不应被视为TValue
状态的一部分。
答案 5 :(得分:0)
看看这个: http://ayende.com/blog/164739/immutable-collections-performance 顺便说一句,使用ImmutableDictionary时要小心