【问题标题】:Implicit cast from IEnumerable<T> to MyCollection从 IEnumerable<T> 隐式转换为 MyCollection
【发布时间】:2014-04-19 14:04:46
【问题描述】:

我正在尝试创建一个隐式转换,允许我使用 LINQ 结果直接返回 MyCollection

public class MyCollection : ICollection<MyType> 
{
    private List<MyType> _list = new List<MyType>();

    public MyCollection(IEnumerable<MyType> collection) 
    {
        _list = new List<MyType>(collection);
    }

    public static implicit operator MyCollection(IEnumerable<MyType> collection) 
    {
        return new MyCollection(collection);
    }

    // collection methods excluded for brevity

    public MyCollection Filter(string filter) 
    {
        return _list.Where(obj => obj.Filter.Equals(filter)); // cannot implicitly convert
    }
}

我之前没有尝试过使用隐式用户定义转换,我做错了什么?

【问题讨论】:

  • 如果MyType 本身就是一个接口,这肯定是重复的吗?我只是在MyCollection 上实现一个接口
  • 你不是从MyType投射,你是从IEnumerable&lt;MyType&gt;投射,IEnumerable&lt;MyType&gt;是一个接口。
  • 啊,是的,感谢您的回答,我现在明白了
  • 尽管将其标记为重复,但必须说 Jon 下面的答案比原始问题中的答案更详细(希望有帮助)

标签: c# casting


【解决方案1】:

当类型转换为或转换为接口类型时,不允许使用implicit。 (如果一种类型是从另一种派生的,那么您也不允许使用它们,因此object 一直是被允许的)。实际上,在这种情况下也不允许您使用explicit。来自ECMA-364 的第 17.9.3 节:

仅当满足以下所有条件时,才允许类或结构声明从源类型S 到目标类型T 的转换,其中S0T0 是从ST 中删除尾随? 修饰符(如果有)所产生的类型:

  • S0T0 是不同的类型。

  • S0T0 是发生运算符声明的类或结构类型。

  • S0T0 都不是接口类型。

  • 排除用户定义的转换,不存在从ST 或从TS 的转换。

您违反了第三条规则(接口类型)和第四条规则(因为已经存在从 MyCollectionIEnumerable&lt;MyType&gt; 的非用户定义转换)。

如果允许的话,我还是建议不要这样做。

仅当效果非常明显时才应使用隐式转换(对于具有合理语言知识的人):long x = 3 + 5int 转换为long 的作用非常明显,@ 的作用非常明显987654347@ 确实将string 转换为object

除非您对implicit 的使用具有相似程度的“明显”,否则这是一个坏主意。

特别是,通常隐式转换不应该在相反方向上隐式,而是应该在一个方向上隐式(在大多数内置情况下为“加宽”方向)并在相反方向上显式(“变窄”方向) “ 方向)。由于您已经获得了从 MyCollectionIEnumerable&lt;MyCollection&gt; 的隐式转换,因此在相反方向上提供隐式转换几乎是个坏主意。

更一般地说,既然你在谈论使用 Linq,使用可扩展的 ToMyCollection() 方法有更大的好处,因为那样你将遵循 ToArray()ToList() 等的 Linq 约定.:

public static class MyCollectionExtensions
{
  public static MyCollection ToMyCollection(this IEnumerable<MyType> collection) 
  {
      return collection as MyCollection ?? new MyCollection(collection);
  }
}

请注意,我测试了集合已经是MyCollection 的情况,以避免浪费的重复构造。您可能还想也可能不想处理List&lt;MyType&gt; 的情况,特别是使用将其直接分配给_list 的内部构造函数。

但是,在这样做之前,您需要考虑允许的混叠效果。如果您知道别名不会导致问题,这可能是一个非常有用的技巧(该类仅在内部使用并且别名已知不是问题,或者别名不会影响MyCollection 的使用,或者别名实际上是可取的)。如果有疑问,那就让方法做return new MyCollection(collection) 更安全。

【讨论】:

  • "请注意,我对集合已经是 MyCollection 的情况进行测试,以避免浪费重复构造"。这又增加了一些令人惊讶的行为,因为通常期望 ToXXX 方法创建新实例。与下一句相同。这将创建完全出乎意料的间接链接。
  • @Euphoric 我确实说过您需要考虑混叠效果,但我会进行编辑以更清楚地说明这一点。
  • @JonHanna 您可能在“扩展”方法中错过了this
  • @decPL 确实如此。谢谢。
【解决方案2】:

您可以使用自定义扩展方法而不是隐式转换:

public static class Extension
{
    public static MyCollection ToMyCollection(this IEnumerable<MyType> enumerable)
    {
        return new MyCollection(enumerable);
    }
}

然后和ToList一样使用:

return _list.Where(obj => obj.Filter.Equals(filter)).ToMyCollection();

【讨论】:

  • 这很好,因为它类似于已经存在的ToListToArray 扩展方法。
  • 如果消费代码对我的集合使用 LINQ 扩展方法会怎样。我是否必须重新实现所有这些,以便它们返回 MyCollection
  • 不,因为MyCollection 已经实现了IEnumerable&lt;MyType&gt;,所以所有的Linq 扩展方法都已经可以使用它了。
  • 让我重新调整,我是否必须重新实现 LINQ 扩展,否则它们会返回 IEnumerable&lt;T&gt;,以便它们按预期返回 MyCollection
  • 这就是为什么你会使用Model.MyList.Where(x =&gt; x.ID == 3).ToMyCollection()。考虑一下,Model.MyList.Where(x =&gt; x.ID == 3).OrderBy(x =&gt; x.ID).Take(5)。如果WhereOrderBy 返回一个MyCollection,那么他们将浪费时间和内存在MyCollection 中构建一个列表,只是为了让它在下一个方法中使用一次然后被收集。重新实现 Linq 仅在您打算执行延迟执行(MyCollection 不会)或者您正在更改对不可变集合的视图时才有意义(同样,这里不是这样)。
【解决方案3】:

这是行不通的实际行:

public static implicit operator MyCollection(IEnumerable<MyType> collection)

因为你正在尝试做什么is not allowed

错误 CS0552:不允许用户定义的与接口之间的转换

如果重新定义类转换的接口,你可能会破坏太多的 CLR 内部,所以你不能这样做。

最好的方法是使用自定义扩展方法,如@Euphoric 建议。

【讨论】:

    【解决方案4】:

    如果您想扩展IEnumerable 的某些功能,您可以轻松使用扩展:

    public static class Extensions
    {
        public IEnumerable<MyType> Filter(this IEnumerable<MyType> e, string filter)
        {
            return e.Where(T => T.Filter.Equals(filter));
        }
    }
    

    即使没有强制转换操作符,它也能正常工作。


    如果您想要隐式转换,this 会告诉您这并不容易。

    【讨论】:

      【解决方案5】:

      为什么需要 ICollection?我会在下面的代码中使用 IEnumerable。如果您需要 ICollection,只需将下面的 IEnumerable 替换为 ICollection,但要保持一致并使用其中一个,而不是两者的混合。

      public class MyCollection : IEnumerable<MyType>
      {
          private IEnumerable<MyType> _list;
      
          public MyCollection(IEnumerable<MyType> collection)
          {
              _list = collection;
          }
      
          // collection methods excluded for brevity
      
          public MyCollection Filter(string filter)
          {
              return new MyCollection(_list.Where(obj => obj.Filter.Equals(filter))); 
          }
      

      请注意:如果您使用 ICollection 而不是 IEnumerable,则 Filter 命令应为:

      return new MyCollection(_list.Where(obj => obj.Filter.Equals(filter)).ToList());

      【讨论】:

      • 因为它是一个集合。不仅仅是一个可枚举的列表。 IEnumerable 不包括 Add、Remove 等。
      猜你喜欢
      • 1970-01-01
      • 2017-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-02
      相关资源
      最近更新 更多