将IEnumerable<T>隐式转换为MyCollection

9

我正在尝试创建一个隐式转换,使我可以使用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上实现了一个接口。 - Alex
1
你不是从 MyType 进行转换,而是从 IEnumerable<MyType> 进行转换,而 IEnumerable<MyType> 是一个接口。 - Jon Hanna
啊,我现在明白了,多亏了你的答案。 - Alex
1
尽管将其标记为重复,但必须说Jon在下面的回答比原问题中的回答更详细(并且希望更有帮助)。 - decPL
5个回答

8

当类型转换的源类型或目标类型为接口类型时,不允许使用implicit(如果一个类型是另一个类型的派生类,也不允许使用)。实际上,在这种情况下,也不允许使用explicit。参见ECMA-364第§17.9.3节。

只有当以下所有条件都为真时,类或结构体才允许声明从源类型S到目标类型T的转换,其中S0和T0是从S和T中删除任何尾随?修饰符后得到的类型:1. S0和T0是不同的类型。2. S0或T0是运算符声明发生的类或结构体类型。3. S0和T0均不是接口类型。4. 排除用户定义的转换,不存在从S到T或从T到S的转换。您违反了第三条规则(接口类型)和第四条规则(因为已经存在从MyCollection到IEnumerable的非用户定义转换)。
如果允许的话,我仍然建议不要这样做。
只有当效果非常明显时(对于具有合理语言知识的人来说),才应使用隐式转换:在将int转换为long时,long x = 3 + 5的作用是非常明显的;在将string转换为object时,object x = "abc"的作用也是非常明显的。
除非你使用的隐式转换具有类似的“明显”水平,否则这是一个坏主意。
特别地,一般情况下,隐式转换不应该在相反的方向上进行隐式转换,而应该在一个方向上进行隐式转换(在大多数内置情况下是“扩展”方向),在相反的方向上进行显式转换(在“缩小”方向上)。由于已经可以从MyCollectionIEnumerable<MyCollection>进行隐式转换,因此在相反的方向上进行隐式转换几乎是个坏主意。
更普遍地说,由于你谈论使用Linq,使用可扩展的ToMyCollection()方法会带来更强大的好处,因为这样你就会遵循Linq的约定,如ToArray()ToList()等。
public static class MyCollectionExtensions
{
  public static MyCollection ToMyCollection(this IEnumerable<MyType> collection) 
  {
      return collection as MyCollection ?? new MyCollection(collection);
  }
}

请注意,我测试集合已经是MyCollection的情况,以避免浪费重复构造。您可能也希望特别处理List<MyType>的情况,使用一个内部构造函数将其直接分配给_list
但是,在这样做之前,您需要考虑可能产生的别名效应。如果您知道别名不会引起问题(要么类仅在内部使用且别名已知不是问题,要么别名不会影响MyCollection的使用,要么别名实际上是可取的),则这可以是非常有用的技巧。如果有疑问,那么只需让方法执行return new MyCollection(collection)以更安全。

请注意,我测试的情况是集合已经是MyCollection,以避免浪费重复的构造。这会导致一些令人惊讶的行为,因为ToXXX方法通常被期望创建新实例。下一句也是如此。那将创建完全意外的间接链接。 - Euphoric
@Euphoric 我确实说过你需要考虑别名效应,但我会修改以使其更清晰。 - Jon Hanna
1
@JonHanna,你可能在你的“扩展”方法中漏掉了this - decPL
1
@decPL 确实。谢谢。 - Jon Hanna

6
您可以使用自定义扩展方法代替隐式转换:
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();

1
这很好,因为它类似于已经存在的 ToListToArray 扩展方法。 - brimble2010
如果使用LINQ扩展方法来消费我的集合,那么我是否需要重新实现它们以便返回MyCollection - Alex
1
不需要,因为MyCollection已经实现了IEnumerable<MyType>接口,所以所有的Linq扩展方法都可以直接使用。 - Jon Hanna
如果你想让它们这样做,那么是的。为什么你想让他们如此呢?通常情况下,就像 ToList 一样,我们只需要在想要 MyCollection 的那个时间点将 ToMyCollection 作为最后一件事情即可。否则,如果你想对它们进行另一个 Linq 操作,那么每个阶段都会创建新的 MyCollection 对象,这将浪费时间和内存。 - Jon Hanna
1
这就是为什么你会使用 Model.MyList.Where(x => x.ID == 3).ToMyCollection()。考虑一下,Model.MyList.Where(x => x.ID == 3).OrderBy(x => x.ID).Take(5)。如果 WhereOrderBy 返回了一个 MyCollection,那么它们将浪费时间和内存在 MyCollection 中构建一个列表,只是为了在下一个方法中使用一次,然后被收集。重新实现 Linq 只有在你要执行延迟执行(MyCollection 不支持)或者也许你正在改变对不可变集合的视图时才有意义(同样,在这里不适用)。 - Jon Hanna
显示剩余2条评论

1

这是实际上不起作用的行:

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

因为你正在尝试做的不被允许

错误 CS0552:不允许对或从接口进行用户定义的转换

如果重新定义接口到类的转换,您可能会破坏太多的CLR内部内容,因此不允许这样做。

最好的方法是使用自定义扩展方法,就像@Euphoric建议的那样。


0

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

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

这将使其即使没有强制转换运算符也能正常工作。


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


0
为什么需要一个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 等方法。 - Alex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接