【问题标题】:Why do C# collection initializers work this way?为什么 C# 集合初始化器以这种方式工作?
【发布时间】:2010-10-02 08:37:13
【问题描述】:

我在查看 C# 集合初始化器,发现它的实现非常实用,但也与 C# 中的其他任何东西都非常不同

我可以创建这样的代码:

using System;
using System.Collections;

class Program
{
    static void Main()
    {
        Test test = new Test { 1, 2, 3 };
    }
}

class Test : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void Add(int i) { }
}

因为我已经满足了编译器的最低要求(实现了IEnumerablepublic void Add),所以这可行,但显然没有任何价值。

我想知道是什么阻止了 C# 团队创建更严格的要求集?换句话说,为什么为了编译这个语法,编译器不要求类型实现ICollection?这似乎更符合其他 C# 功能的精神。

【问题讨论】:

  • 请注意 Web 开发人员:如果您尝试将该类的实例序列化为 JSON,则会引发 NotImplementedException(实现 IEnumerable 会导致序列化程序遍历对象)

标签: c# collections


【解决方案1】:

您的观察是正确的 - 事实上,它反映了由 Microsoft C# 语言 PM 的 Mads Torgersen 所做的观察。

Mads 在 2006 年 10 月就这个主题发表了一篇题为 What Is a Collection? 的帖子,其中他写道:

承认,我们一开始就搞砸了 框架的版本 System.Collections.ICollection,其中 旁边是没用的。但我们修好了 当泛型出现时非常好 在 .NET 框架 2.0 中: System.Collections.Generic.ICollection 让您添加和删除元素, 枚举它们,计算它们并检查 会员资格。

显然,从那时起,每个人都会 每次都执行 ICollection 他们做一个集合,对吧?不是这样。 下面是我们如何使用 LINQ 来学习 关于集合的真正含义,以及 这如何让我们改变了我们的语言 使用 C# 3.0 进行设计。

原来框架中ICollection<T>的实现只有14个,但是有189个类实现了IEnumerable并有一个公共的Add()方法。

这种方法有一个隐藏的好处 - 如果他们基于 ICollection<T> 接口,那么就会有一个完全受支持的 Add() 方法。

相比之下,他们确实采用的方法意味着集合的初始化器只是为 Add() 方法形成参数集。

为了说明,让我们稍微扩展一下您的代码:

class Test : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        throw new NotImplementedException();
    }

    public void Add(int i) { }

    public void Add(int i, string s) { }
}

你现在可以这样写:

class Program
{
    static void Main()
    {
        Test test 
            = new Test 
            {
                1, 
                { 2, "two" },
                3 
            };
    }
}

【讨论】:

  • 在引用中,第 2 段第 1 行,它是“ICollection”而不是“ICollection”。
  • 你说得对——我忽略了对小于和大于字符进行编码。固定。
  • 您的回答体现了这提供的好处/灵活性,但我想知道这是否意味着在类似情况下没有有效的方法来提供这种灵活性。他们有效地写入规范“如果你碰巧在实现IEnumerable的层次结构中的某个地方有一个名为Add的方法,那么我们将在编译器中做一些hokus-pokus并让初始化程序调用它,即使该方法是不是重载、接口成员,或以任何方式通过语法、属性或其他方式指定用于此类目的。”
  • 即我认为 OP 的问题是“是什么阻止了 C# 团队创建一组更严格的要求?”有点开放。您很好地说明了为什么按原样提供了很大的灵活性。要回答这个问题,基本上将涉及 1) 可能的替代方案的详尽列表以及为什么它们不能很好地工作,你谈到了一些,或者 2) 一个在没有编译器 hokus-pokus 的情况下实现相同好处的示例。如果#2 存在,我从 API/插件设计的角度很好奇。 IE。你实现了几个参数委托,我的框架会选择最好的。
  • 并不是说我希望答案能够真正完成#1 或#2,因为你永远不知道你是否有#1 的完整列表或#2 是否存在。这也不是对您出色答案的批评。他们这样做似乎令人惊讶。我今天才读到这个特性的规范,它似乎真的不适合 C#
【解决方案2】:

这个我也想过,最让我满意的答案是ICollection除了Add之外还有很多方法,比如:Clear、Contains、CopyTo、Remove。删除元素或清除与是否能够支持对象初始化器语法无关,您所需要的只是一个 Add()。

如果框架设计得足够细致,并且有一个 ICollectionAdd 接口,那么它就会有一个“完美”的设计。但老实说,我认为每个接口都有一个方法不会增加太多价值。 IEnumerable + Add 似乎是一种 hackish 方法,但仔细想想,它是一个更好的选择。

编辑:这不是 C# 唯一一次解决此类解决方案的问题。从 .NET 1.1 开始,foreach 使用鸭子类型来枚举集合,您的类需要实现的只是 GetEnumerator、MoveNext 和 Current。 Kirill Osenkov 有一个post,它也会问你的问题。

【讨论】:

  • 这就是“接口隔离原则”。
【解决方案3】:

(我知道我在这方面迟到了 3 年,但我对现有的答案并不满意。)

为什么,为了编译这个语法,编译器不 要求该类型实现 ICollection?

我会颠倒你的问题:如果编译器有一些并不真正需要的要求,那有什么用?

ICollection 类也可以从集合初始化语法中受益。考虑允许向其中添加数据但不允许访问之前添加的数据的类。

就我个人而言,我喜欢使用 new Data { { ..., ... }, ... } 语法来为我的单元测试代码添加一种轻量级、类似 DSL 的外观。

其实我宁愿弱化这个要求,这样我就可以使用好看的语法,而不必费心去实现IEnumerable。集合初始化器是 Add() 的纯语法糖,它们不应该需要任何其他东西。

【讨论】:

  • 我明白你的意思,但如果语法糖需要一种方法,我宁愿通过接口了解它。
  • @nik.shornikov 类似 ICollectionInitializerSyntax 的东西(以及所有的 变体)?我同意,这将使代码的意图更加清晰。这很符合我削弱当前 IEnumerable 要求的愿望。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-01
相关资源
最近更新 更多