泛型的隐式转换运算符不能用于接口。

14

我基本上有以下类(示例来自C#创建泛型类的隐式转换?)。

class MyClass<T>
{
  public MyClass(T val)
  {
     Value = val;
  }

  public T Value { get; set; }

  public static implicit operator MyClass<T>(T someValue)
  {
     return new MyClass<T>(someValue);
  }

  public static implicit operator T(MyClass<T> myClassInstance)
  {
     return myClassInstance.Value;
  }
}

您可以执行以下操作:

MyClass<IFoo> foo1 = new Foo();
MyClass<Foo>  foo2 = new Foo();

//But not
MyClass<IFoo> foo3 = (IFoo)new Foo();

当尝试执行类似下面这样的操作时,真正的问题出现了

void Bar(IFoo foo)
{
    Bar2(foo);
    //What should be the same as
    Bar2<IFoo>(new MyClass<IFoo>(foo));
}

void Bar2<T>(MyClass<T> myClass)
{
     //Do stuff
}

我该如何重构MyClass,以便在只知道接口的情况下就能够使用对象?


我不明白你真正想通过这个实现什么。MyClass是否总是使用IFoo对象(接口和实现),或者它可以使用其他任何东西?无论如何,如果你试图隐式转换一个接口,在C#中是不可能的(正如@EricLippert已经说过的那样)。 - romain-aga
将会有多个IFoo的实现(Foo1,Foo2,.. FooN)。可能还会有其他接口Bar(OtherInterface other){ Bar2 <OtherInterface>(other); } 当然,问题是实际问题的简化版本。 - Stijn Van Antwerpen
2个回答

26

简短回答:

用户定义的隐式转换在接口上不起作用。不要试图使其工作。找到另一种解决类型系统问题的方法。

长篇回答:

这是C#设计团队的一个有意决定。原则是,在涉及接口的转换时,您希望保留引用标识; 您正在询问实现接口的对象的标识,而不是尝试创建具有类似属性的类似对象。

更大的原则在于,用户定义的转换不应替换内置转换。但是,由于几乎任何类都可以被子类化,并且该子类可以实现几乎任何接口,因此很难静态地知道涉及接口的给定用户定义转换是否可能替换内置转换。

顺便说一下,这是规范中特别棘手的部分,而C#编译器在这里存在一些错误。我怀疑你上面的某个案例利用了这些漏洞;真实世界中存在这样的程序阻止我修复这些漏洞。

这些漏洞主要是由于在泛型引入许多未预见到的复杂性之前设计了这个功能,然后在泛型引入后没有进行足够的重新设计。

有关详细信息,请参见我在此处的广泛评论,特别是标记为DELIBERATE SPEC VIOLATION的部分,其中描述了与接口转换相关的问题。

https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/UserDefinedImplicitConversions.cs

正如您所看到的,这个文件不到一千行,可能一半以上都是注释。需要经过几周的仔细研究和与语言团队的讨论才能将这些语义梳理清楚。一旦在编译器中犯了错误,您通常必须在十年后彻底理解它,然后永久地存储它以便在升级时不破坏客户的代码。从C#在规范的这个模糊部分出错的角度来看,有很多给语言设计者的教训。

我该如何重构MyClass,以便在只知道接口时能够使用对象呢?

不要尝试。将接口引用转换为实际运行时类型,然后从那里开始使用它。或直接创建所需类型的实例,而不是通过隐式转换。不要尝试使用隐式转换和接口玩游戏;这样做会带来不好的结果。


有Foo1,Foo2,...,FooN。重点是MyClass不应该关心,因为Bar()不会知道。 (将来可能会有FooX)。因此,转换为实际运行时类型不是一个选项。 - Stijn Van Antwerpen
确实如此。如果我写了´void Bar(MyClass<IFoo> foo)´,那么我可以注入任何IFoo实现(Foo1、Foo2、..、FooN、FooX),因为在那个点上隐式转换是有效的... - Stijn Van Antwerpen
关于Unity的编程问题,我有一个Map<T>(仅包含T[,])的情况,我想能够编写public static implicit operator Texture2D(Map<Color> map)public static implicit operator Texture2D(Map<float> map)public static implicit operator Texture2D(Map<byte> map)方法,但是我得到了CS0556错误,有没有办法解决这个问题? - user3915050

2
使用“dynamic”关键字进行赋值。稍后可以进行区分。最初的回答:

使用“dynamic”关键字进行赋值。稍后可以进行区分。

  var hook = Environment.Version < new Version(4, 0) ? (dynamic)
    // .NET 2.0->3.5        
    new JITHook<MscorjitAddrProvider>() :
    // .NET 4.0+
    new JITHook<ClrjitAddrProvider>();

确认无误。对于我来说,在 (MyClass)(otherClassObject as dynamic) 中,其中 otherClassObject 是一个接口类型,会触发在 MyClass 上定义的隐式转换。如果没有 as dynamic,则不会触发转换。 - alelom

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