将一个通用类型隐式转换为包装器

6

我希望在函数返回值时自动将其包装在一个泛型容器中(我知道这并不总是理想的,但对于我的情况来说很有意义)。例如,我想编写以下代码:

public static Wrapper<string> Load() {
    return "";
}

我可以通过将以下内容添加到我的Wrapper类来实现此功能:
public static implicit operator Wrapper<T>(T val) {
    return new Wrapper<T>(val); 
}

很不幸,当我尝试转换一个 IEnumerable 时,它失败了。下面是完整的代码(在ideone上查看):

public class Test {
    public static void Main() {
        string x = "";
        Wrapper<string> xx = x;

        string[] y = new[] { "" };
        Wrapper<string[]> yy = y;

        IEnumerable<string> z = new[] { "" };
        Wrapper<IEnumerable<string>> zz = z; // (!)
    }
}
public sealed class Wrapper<T> {
    private readonly object _value;
    public Wrapper(T value) {
        this._value = value;
    }
    public static implicit operator Wrapper<T>(T val) { return new Wrapper<T>(val); }
}

我得到的编译错误是:

无法隐式转换类型“System.Collections.Generic.IEnumerable<string>”为“...Wrapper<System.Collections.Generic.IEnumerable<string>>”

发生了什么情况,我该如何修复它?


1
@StriplingWarrior 足够了吗? - mk.
2
IEnumerable<string> z = new [] {""}; 替换为 var z = new [] {""} 即可解决问题。Ideone 示例 - vendettamit
2
@vendettamit 为了明确,这是因为 var 隐式地将类型定义为 string[] 而不是 IEnumerable<string> - 31eee384
2
31eee384对于解释发生这种情况的原因做得非常出色。如果您想要一个更简单的语法解决方法,您可以在Wrapper上创建一个静态的通用方法来帮助人们避免指定所有泛型。我在我的Maybe类型中做了这件事,所以您可以说 var zz = Maybe.From(z);,例如。 - StriplingWarrior
2
我同意@StriplingWarrior的观点,总的来说,我认为你的代码中应该尽量避免使用隐式操作符。当阅读代码时很难发现它们,很难在IDE中查找,并且显然不能与接口一起使用! - 31eee384
显示剩余4条评论
1个回答

6
原因在于C#规范的一部分,正如这个答案所指出的:

如果以下均为真,则允许类或结构体声明从源类型S到目标类型T的转换:

  • ...
  • S和T都不是object接口类型

不允许用户定义的转换从或者转换到接口类型。特别地,此限制确保在转换为接口类型时没有用户定义的转换发生,并且仅当被转换的对象实际上实现了指定的接口类型时,转换为接口类型才会成功。

来源

你的隐式转换在不同的使用方式下可以正常工作,例如以下代码:
using System;
using System.Collections.Generic;

public class Wrapper<T>
{
    public T Val { get; private set; }

    public Wrapper(T val)
    {
        Val = val;
    }

    public static implicit operator Wrapper<T>(T val)
    {
        return new Wrapper<T>(val); 
    }
}

public class Test
{
    public static Wrapper<IEnumerable<int>> GetIt()
    {
        // The array is typed as int[], not IEnumerable<int>, so the
        // implicit operator can be used.
        return new int[] { 1, 2, 3 };
    }

    public static void Main()
    {
        // Prints 1, 2, 3
        foreach (var i in GetIt().Val)
        {
            Console.WriteLine(i);
        }
    }
}

您遇到的具体问题是因为在返回数组之前,您将其存储在一个IEnumerable<string>的本地变量中。关键是传递给隐式运算符的变量类型:因为源类型S在您的本地变量上是IEnumerable<int>,所以无法使用该运算符。int[]不是接口,因此可以使用它。

1
添加了一条注释——结果发现如果将其存储在本地变量中,我可以重现它。这很奇怪...不知道是什么原因,但解决方法是不使用本地变量! - 31eee384
1
我现在要取消我的+1投票,因为问题已经变成了如何重现,并且你提到的规范部分并没有解释这种行为。(PS--这不是一个mono问题:LINQPad产生相同的结果。) - StriplingWarrior
1
如果我们使用 var a = new int[] { 1, 2, 3 };,它是可以工作的。根据规范,看起来限制是针对接口的。 - vendettamit
2
一个类或结构体可以声明从源类型 S 到目标类型 T 的转换,前提是 [...] 既不是 S 也不是 T 是对象或接口类型。在这个复制的案例中,SIEnumerable<int>。这样做有问题吗?(我回应的评论似乎已被删除,对于缺乏上下文表示抱歉。) - 31eee384
1
没关系,正如你可以从我热切的编辑中看出来的那样,我今天才学会这个。 :) 顺便说一句,你的编辑很好,因为问题包含一个 repro,所以它没有太多意义。 - 31eee384
显示剩余8条评论

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