.NET 中真正不可变的字典

发布于 2024-10-26 01:55:15 字数 295 浏览 3 评论 0原文

早上好,下午好,晚上好,

仍然以我关于 .NET 中的 不可变字典 的问题为基础,我提出了有以下问题:如果 TKeyTValue 是值类型,则可以使字典真正不可变,因为它的内部结构和值本身都不能改变,如果这些参数是引用类型,则键和值可以轻松更改,从而更改字典本身。我说得对吗?

非常感谢。

Good morning, afternoon or night,

Still building on my question about immutable dictionaries in .NET, I came up with the following question: while if then TKey and TValue are value types you can make a dictionary truly immutable, in the sense that neither its inner structure nor the values themselves can change, if those parameters are reference types keys and values can easily be changed, thus changing the dictionary itself. Am I right?

Thank you very much.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

深爱成瘾 2024-11-02 01:55:29

看看新的 BCL 不可变集合:
http ://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx

这仍然是预览版本,但它包含以下类型:

  • ImmutableStack
  • ImmutableQueue
  • ImmutableList
  • ImmutableHashSet
  • ImmutableSortedSet
  • ImmutableSortedDictionary
  • ImmutableSortedDictionary

我希望 C# 的下一个版本将包含用于指定不变性约束的关键字类级别(类似于此处建议的内容),以及克隆的方法更容易地访问对象,例如 使用关键字 <在 F# 中可以完成的操作代码>与:

var o1 = new CustomObject { Field1 = 0, Field2 = 3 }
var o2 = o1 with { Field1 = 1} 

Take a look at the new BCL Immutable Collections:
http://blogs.msdn.com/b/bclteam/archive/2012/12/18/preview-of-immutable-collections-released-on-nuget.aspx

This is still a preview release but it contains the following types:

  • ImmutableStack<T>
  • ImmutableQueue<T>
  • ImmutableList<T>
  • ImmutableHashSet<T>
  • ImmutableSortedSet<T>
  • ImmutableDictionary<K, V>
  • ImmutableSortedDictionary<K, V>

I wish the next version of C# will include keywords to specify immutability constraints at the class level (similarly to what is suggested here), and a way to clone objects more easily, like what can be done in F# with the keyword with :

var o1 = new CustomObject { Field1 = 0, Field2 = 3 }
var o2 = o1 with { Field1 = 1} 
怪我入戏太深 2024-11-02 01:55:29

这是一个非常快速的设计,可能不会直接编译,但希望能给你一些想法......

[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.");
        }
    }
}

Here's a very quick design, probably wont compile straight off but hopefully will give you some ideas...

[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.");
        }
    }
}
原来分手还会想你 2024-11-02 01:55:29

看看这个:
http://ayende.com/blog/164739/immutable-collections-performance
顺便说一句,使用 ImmutableDictionary 时要非常小心

take a look at this:
http://ayende.com/blog/164739/immutable-collections-performance
by the way be completely careful when using ImmutableDictionary

流星番茄 2024-11-02 01:55:29

要了解发生了什么,请下拉至比字典更简单的类型:List。如果创建一个 List,在其中抛出一些项目,然后创建将其传递给 ReadOnlyCollection 的构造函数,并销毁除该列表之外的所有引用包含在 ReadOnlyCollection 中的列表,那么该列表将是不可变的。无论 T 是可变的还是不可变的、类还是结构,它的状态都永远不会改变。如果 T 是可变类类型,则任何将此类列表视为可变的人都会失败,因为该列表将错误地视为持有对象。事实并非如此。

如果 T 是类类型,则 List保存个对象 - 它识别 它们。 如果如上所述将 List 设为不可变,则在添加了五个 int 数组引用后,则如下只要它存在,它将始终保存对这五个数组的引用。数组的内容可能会更改,但List引用的对象的属性不构成列表状态的一部分

Dictionary 的状态比 List 的状态更难定义,尤其是考虑到可能导致混乱状态的情况下由古怪的 GetHashCode() 实现实现,但同样的原则也适用。如果 TKey 是类类型,则构成字典状态一部分的 TKey 实例的唯一方面是 GetHashCode 返回的值,并且由其 Equals 方法隐含的等价类;这两件事都应该是不可变的。 TKeyTValue 类对象的合法可变方面不应被视为 TDictionary 状态的一部分。

To understand what's going on, drop down to a type simpler than a dictionary: a List<T>. If one creates a List<T>, throws some items in it, then creates passes it to the constructor of ReadOnlyCollection<T> and destroys all references to that list other than the one contained within the ReadOnlyCollection<T>, then that list will be immutable. Its state will never change, regardless of whether T mutable or immutable, class or struct. Anyone who would regard such a list as mutable if T is a mutable class type fails is mistakenly regarding the list as holding objects. It doesn't.

If T is a class type, a List<T> does not hold objects--it identifies them. If a List<int[]> is made immutable as described above, after five int array references have been added to it, then as long as it exists it will always hold references to those same five arrays. The contents of the arrays may change, but the properties of the objects referred to by a List<T> form no part of the list's state.

The state of a Dictionary<TKey,TValue> is a little harder to define than the state of a List<T>, especially if one considers the possibilities of muddled states caused by wonky GetHashCode() implementations, but the same principles apply. If TKey is a class type, the only aspects of a TKey instance which form part of the dictionary's state are the value returned by GetHashCode, and the equivalence class implied by its Equals method; both of those things are supposed to be immutable. No legitimately-mutable aspect of a TKey or TValue class object should be regarded as part of the state of a TDictionary<TKey,TValue>.

小清晰的声音 2024-11-02 01:55:28

结构是值类型,不一定是不可变的 - 所以答案是否定的。您可以设计不可变类型(使所有字段和属性只读)。但是,没有可用的类型约束(例如 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++;

我承认有点构造。

Structs are value types and are not necessarily immutable - so the answer is no. You can design immutable types (make all fields and properties readonly). However there is no type constraint available (like where TKey : class) which would allow you to enforce it.

Update: An example:

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

A bit constructed I admit.

行雁书 2024-11-02 01:55:27

如果 TKey 和 TValue 是值类型,则可以使字典真正不可变,即它的内部结构和值本身都不能更改,如果这些参数是引用类型键和值可以轻松更改,从而更改字典本身。我说得对吗?

不完全是。您已经发现了一个真正的问题,但您对问题的描述尚未经过充分考虑。

首先,我注意到,是的,固定在不可变字典中的值类型是不可变的,即使它是可变值类型。为什么?因为值类型只能通过改变包含它们的变量来改变。如果您的字典没有公开用于存储值类型的变量,那么就无法更改这些变量。

然而,即使值类型本身是不可变的,不可变值类型也可以包含对可变引用类型的引用,现在我们遇到了同样的问题。将字典限制为值类型只会将问题推向更高层次,而并不能解决问题!保证“深度”不变性真正需要的是blittable值类型的字典。我所说的“blittable”是指没有引用类型字段的值类型。 (之所以这么说,是因为您可以通过将位直接“传输”到磁盘来将其中之一序列化到存储。)

不幸的是,通用类型系统中没有限制可传输的值类型。

现在让我们考虑不可变字典中可变引用类型的一般问题,无论这些引用类型是直接存在还是通过值类型的字段存在。如果引用类型在不可变字典中时发生带外变异,会出现什么问题?

好吧,首先想到的是不可变字典应该对同一问题两次给出相同的答案,但现在情况不再如此了。如果您说“customers[name].Address”,您预计会两次获得相同的答案,但如果客户是可以变异的引用类型,您可能会得到不同的答案。这可能是可取的,也可能是不可取的。 (请注意,字典给出了相同的答案两次:它给出了对客户对象的相同引用。实际上是客户对象没有两次给出相同的答案。)

假设您不'不要试图记住可能改变的问题的答案,这通常不是一个大问题。

一个更大的问题是,当哈希表中作为键的对象发生变异时,会更改其哈希值并“丢失”表中的对象。

如果发生这种情况,那么有人就没有遵守指导方针。指导原则是 (1) 引用类型如果可能的话,不应将其哈希码(和相等性)基于可能发生变异的数据,以及 (2) 用作哈希表中的键的对象不应发生变异。

if then TKey and TValue are value types you can make a dictionary truly immutable, in the sense that neither its inner structure nor the values themselves can change, if those parameters are reference types keys and values can easily be changed, thus changing the dictionary itself. Am I right?

Not exactly. You've identified a real problem but your characterization of it is not fully thought through.

First off, I note that yes, a value type stuck in an immutable dictionary is immutable, even if it is a mutable value type. Why? Because value types are only mutated by mutating the variable which contains them. If your dictionary does not expose the variables it uses to store the value types then there is no way for those variables to be changed.

However, even if a value type is itself immutable an immutable value type can contain a reference to a mutable reference type, and now we've got the same problem. Restricting the dictionary to value types only pushes the problem off a level, it does not solve it! What you really need to guarantee "deep" immutability is a dictionary of blittable value types. By "blittable", I mean a value type with no fields of reference type. (So-called because you can serialize one of these to storage by "blitting" the bits straight out to disk.)

Unfortunately there is no constraint in the generic type system to constrain to blittable value types.

Let's now consider the general problem of mutable reference types in an immutable dictionary, whether those reference types are there directly or via a field of a value type. What can go wrong if a reference type is mutated out-of-band while it is in an immutable dictionary?

Well, the first thing that comes to mind is that an immutable dictionary should give the same answer twice to the same question twice, and now this is no longer the case. If you say "customers[name].Address" you'd expect to get the same answer twice, but if the customer is a reference type that can mutate, you'll get a potentially different answer. That might or might not be desirable. (And note that the dictionary is giving the same answer twice: it's giving the same reference to the customer object. It is actually the customer object that is not giving the same answer twice.)

Assuming you don't try to memoize answers to questions that can change, this is usually not a big problem.

A bigger problem is when an object that is in a hash table as a key mutates, thereby changing its hash value and "losing" the object in the table.

If that happens then someone is not keeping inside the guidelines. The guidelines are (1) reference types should if possible not base their hash codes (and equality) upon data that can mutate, and (2) an object being used as a key in a hash table should not be mutated.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文