重载决议中不考虑隐式转换?

Implicit conversions not considered in overload resolution?

我正在尝试包装一个类型(在我的控制之外),以便它看起来无缝地实现了一个接口(也在我的控制之外)。

给出这些定义

// External types. Not changable.
class Foo {
    public int I { get; set; }
    public int J { get; set; }
}
interface IGenerateSignature {
    string Generate();
}

我想使用 Foo 实例来调用带有 IGenerateSignature 参数的方法:

void Test() {
    var foo = new Foo { I = 1, J = 2 };
    GetSignature(foo);
}

void GetSignature(IGenerateSignature sig) {
    Console.Write(sig.Generate());
}

我试过像这样创建一个中间结构:

struct FooSignaturizer : IGenerateSignature {
    private readonly Foo _foo;
    public FooSignaturizer(Foo f) {
        _foo = f;
    }
    public static implicit operator FooSignaturizer(Foo f) {
        return new FooSignaturizer(f);
    }
    public string Generate() {
        return _foo.I + ":" + _foo.J;
    }
}

但由于某些原因,重载解析无法找到从 FooFooSignaturizer 的转换,我得到一个 "Cannot convert" 编译器错误。如果我手动添加演员,GetSignature((FooSignaturizer) foo),它会起作用。但是,我还需要添加对 BarQux 类型以及 BarSignaturizerQuxSignaturizer 类型的支持,因此转换不适用于这些情况。

有没有办法做到这一点?

根据 C# 规范的 7.5.3.1,仅考虑从参数表达式到参数类型的隐式转换。

7.5.3.1 Applicable function member

A function member is said to be an applicable function member with respect to an argument list A when all of the following are true:

  • Each argument in A corresponds to a parameter in the function member declaration as described in §7.5.1.1, and any parameter to which no argument corresponds is an optional parameter.
  • For each argument in A, the parameter passing mode of the argument (i.e., value, ref, or out) is identical to the parameter passing mode of the corresponding parameter, and
    • for a value parameter or a parameter array, an implicit conversion (§6.1) exists from the argument to the type of the corresponding parameter, or
    • for a ref or out parameter, the type of the argument is identical to the type of the corresponding parameter. After all, a ref or out parameter is an alias for the argument passed.

这里不是从 FooIGenereateSignature 的隐式转换,它是一个包装器。

作为对此行为的解释,您不能要求编译器遍历范围内 IGenerateSignature 的每个实现以查看它是否具有隐式转换 to/from Foo .如果不止一个呢?


关于如何为 FooBarQux 实现此目标...

您想要实现的目标是调用 GetSignature(fooOrBarOrQux),这是不可能的,因为(根据您对 Foo 的描述)您不能有一个变量可以a Foo or a Bar or a Qux 在编译时 - 它们是不相关的。您将始终需要三个调用站点,因此没有理由不对这三种情况进行三种略有不同的转换(包装器 class 或重载方法调用或其他)。

...除非你使用 dynamic?

Rawling 的回答很好地解释了您遇到问题的原因。至于如何实现你想要的。我可能会考虑这样的事情:

public interface ISignaturizer
{
    IGenerateSignature ToSignaturizer();
}

struct FooSignaturizer : IGenerateSignature, ISignaturizer{
    private readonly Foo _foo;
    public FooSignaturizer(Foo f) {
        _foo = f;
    }

    public string Generate() {
        return _foo.I + ":" + _foo.J;
    }

    public IGenerateSignature ToSignaturizer()
    {
        return (IGenerateSignature)this;
    }
}

现在BarSignaturizerQuxSignaturizer可以实现相同的接口。然后你可以这样做:

GetSignature(((ISignaturizer)fooOrBarOrQux).ToSignaturizer());

这不是很优雅,但是我认为应该可以满足您的需求。

Rawling 的回答很好地解释了您遇到问题的原因。由于您无法通过隐式转换解决此问题,因此您可以尝试使用扩展方法将所有类型转换为 IGenerateSignature,如下所示:

void Test() {
    var foo = new Foo { I = 1, J = 2 };
    GetSignature(foo.AsIGenerateSignature());
}

void GetSignature(IGenerateSignature sig) {
    Console.Write(sig.Generate());
}

public static class GenerateSignatureExtensions
{
    public static IGenerateSignature AsIGenerateSignature(this IGenerateSignature me)
    {
        return me;
    }
    public static IGenerateSignature AsIGenerateSignature(this Foo me)
    {
        return new FooSignaturizer(me);
    }
    public static IGenerateSignature AsIGenerateSignature(this Bar me)
    {
        return new BarSignaturizer(me);
    }
    //....

}