隐式转换无法赋值给接口。

3

我有一个类,其中定义了一个从string进行隐式转换的方法:

class TestClass : ITestInterface
{
    public static implicit operator TestClass(string value)
    {
        return new TestClass(value);
    }

    public TestClass(string value)
    {
        Value = value;
    }
    public string Value { get; set; }
}

它实现了一个标记接口:

public interface ITestInterface { }

我有一个定义为:

另一个类的方法: 的方法。

public void DoSomething(ITestInterface thing) { }

我在尝试调用该方法时出现错误:

public void Test()
{
    TestClass a = "This works fine.";
    DoSomething("Why doesn't this work?");
}

无法从“string”转换为“Tests.ITestInterface”

所有代码都大大简化了;实际需求要复杂得多,但这似乎是阻止我想要实现的模式的核心。

这是什么阻止了它的工作?(C#规范中的某些内容?)
我能对我的代码做出哪些更改以使此类型的转换起作用吗?


4
虽然从字符串到你的类有隐式转换,但是从字符串到你的接口没有。你需要显式地将字符串转换为你的类。然而,尝试将字符串转换为你的类似乎很奇怪。这样做会暗示着每个字符串都可以安全地转换为你的类,我认为这不是你想要的。 - MakePeaceGreatAgain
2
TestClass a = new TestClass("This works fine."); 这段代码有什么问题吗?在我看来,它更加用户友好。 - Liam
在这种特定情况下,每个“string”都是有效的转换。在我的完整代码中,这是“文档”的表示的一部分。可以通过类似于Document(params IPart[] parts)的构造函数创建文档。我正在尝试使其易于包含混合了其他类型内容的“string”块。 - Bradley Uffner
但即使在文档上,我想您也有一些固定的数据结构。我怀疑“我的任意字符串”是否是您类的有效表示形式,不是吗? - MakePeaceGreatAgain
该类专门设计为任意字符串内容的包装器。 - Bradley Uffner
3个回答

6
您忽略了解释问题的第三个选项:
//1
TestClass a = "This works fine.";

//2
ITestInterface i = "This doesn't work either!";

//3
DoSomething("Why doesn't this work?");

(1)中,您声明了TestClass a。这意味着编译器知道当您使用不同类型(在本例中为字符串)时,它应该尝试将该值转换为TestClass
(2)中,您声明了ITestInterface i。这意味着编译器知道当您使用不同类型(在本例中为字符串)时,它应该尝试将该值转换为ITestInterface
这就是问题的根源。在stringITestInterface之间没有定义转换。
您目前正在考虑的是:

好吧,我知道我想要将其转换为TestClass。为什么编译器不能弄清楚我想要做什么?

简短的答案是编译器拒绝猜测。 您想要发生的事情会导致不可能的情况。例如,如果有第二个类也实现了ITestInterface,会发生什么?
class SecondTestClass: ITestInterface
{
    public static implicit operator SecondTestClass(string url)
    {
        return new SecondTestClass(url);
    }

    public SecondTestClass(string url)
    {
        Value = GetValueFromTheInternet(url);
    }
    public string Value { get; set; }
}

让我们重新评估您的代码:

//1
TestClass a = "This works fine.";

这是有效的。从 string 转换为 TestClass

//2
SecondTestClass b = "This works fine.";

这个代码有效。它将 string 转换为 SecondTestClass

//3
ITestInterface i = "This still doesn't work!";

//4
DoSomething("This doesn't work for the same reason as //3");

这个无法工作。编译器不支持将“string”转换为“ITestInterface”的任何已知转换方式。
编译器无法确定您是否希望将其转换为“TestClass”,然后将其分配给“i”,还是希望将其转换为“SecondTestClass”,然后将其分配给“i”。
正如我之前所说,编译器拒绝猜测。
此外,仅为了明确起见,以下内容可以工作:
TestClass a = "This works fine.";

ITestInterface i1 = a;
DoSomething(a);
DoSomething(i1);

SecondTestClass b = "This works fine.";

ITestInterface i2 = b;
DoSomething(b);
DoSomething(i2);

所有这些赋值都有效。

问题的关键是编译器希望您明确地声明要将字符串转换为哪种类型。在您自己的示例中,您已经明确要求一个TestClass。请注意,如果您使用了var,那么这不起作用,因为编译器在这种情况下也无法解决它。


谢谢,我漏掉了多个潜在类实现隐式转换的情况。 - Bradley Uffner

3
编译器的错误信息已经非常明显了,不是吗?DoSomething需要一个ITestInterface实例,而string并没有实现该接口。尽管从string到你的类存在一种隐式转换,但这种转换并不适用于实现该接口的任何其他类。
想象一下还有另一个实现接口的类:
class AnotherTestClass : ITestInterface { }

现在应该如何解决DoSomething调用?转换应该适用于TestClass的实例还是AnotherTestClass?特别是如果AnotherClass也定义了一个隐式转换运算符时,就会出现问题。
或者考虑另一种情况:当您只有接口而没有实现它的类时(这在设计API时非常常见),根本没有转换。您的设计从接口到其具体实现引入了一些静态绑定,这是一件坏事。实际上,这使得您的DoSomething方法仅适用于TestClass类型的实例,这与将接口作为参数使用的目的相矛盾。因此,使用您的API的任何人都只能向您的方法提供TestClass的实例。
除此之外,我怀疑在这里使用强制转换是否明智。通过进行隐式转换,您暗示每个字符串都可以安全地转换为您的类而不丢失任何信息。例如,URI是否是您的类的有效表示?或者您提供的"Why doesn't this work?"
另一方面,期望一个字符串的构造函数更加精确,并且清楚地表明了意图:
var m = new TestClass(myString);

根据我的经验,只有极少数情况下您真正需要隐式转换。更常见的情况是基于某些输入创建一些实例,然后将更多数据附加到该实例上。在您的示例中,这意味着TestClass包含一些字符串信息,但也可能有更多数据。


谢谢,我一直缺少多个潜在类实现隐式转换的情况。 - Bradley Uffner
@BradleyUffner,根据你的设计,你在接口和具体类之间有一些静态绑定。通常使用接口是为了与任何实现独立 - MakePeaceGreatAgain
我正在尝试模仿XElement构造函数使用的相同模式,该模式允许开发人员通过节点、属性和字符串内容构建文档树。我只想包含更多类型安全性(XElement使用object数组)。 - Bradley Uffner
虽然我确实质疑是否需要强制转换,但我不会说使用它们是明显错误的。请注意,“转换为对象”和“有效”是两个非常不同的特征。我也可以这样做:new Person() { Name = "http://google.com" };。犯错误的可能性本身并不是不应该使此属性公开可设置的有效理由。(如果走极端,这将导致您不向消费者泄漏原始类型,而是强制他们使用您的数据类)。 - Flater
在字符串转换和只有一个字符串参数的构造函数之间的选择是一种风格。隐式转换往往会降低可维护性,因为它降低了可读性。然而,如果当前代码库中存在大量的字符串到对象转换,那么当开发人员从本质上理解该转换时,简化该代码为隐式转换可能是有益的。例如,(摘自今天的另一篇帖子):Airport destination = "LAX"; - Flater

0
public void DoSomething(ITestInterface thing) { }

因为参数是一个接口,你想调用 static implicit operator TestClass(string value) 但这是不可能的。在C#中,接口不能有 static 方法。 你只能将类 TestClass 作为参数传递。

public static void DoSomething(TestClass thing) { Console.WriteLine(thing.Value); }

2
这里的问题不是由于运算符类的静态性质,也不是由于接口无法拥有静态成员所导致的。如果 OP 使用基类和派生类而不是接口和实现类,他的问题也会出现,此时您的论点就不再适用了。(尽管如果他使用基类,我希望 OP 的问题源会更加明显)。 - Flater

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