Search code examples
c#implicit-conversionoverload-resolution

Implicit conversions not considered in overload resolution?


I'm trying to wrap a type (outside of my control) so that it would seamlessly appear to implement an interface (also outside of my control).

Given these defintions

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

I would like to use a Foo instance to call a method with an IGenerateSignature parameter:

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

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

I tried creating an intermediary struct like this:

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;
    }
}

But for some reason overload resolution fails to find the conversion from Foo to FooSignaturizer, and I get a "Cannot convert" compiler error. If I manually add a cast, GetSignature((FooSignaturizer) foo), it works. However, I need to also add support for the Bar and Qux types, with BarSignaturizer and QuxSignaturizer, so the cast won't work for those cases.

Is there a way to accomplish this?


Solution

  • As per 7.5.3.1 of the C# spec, only implicit conversions from argument expression to parameter type are considered.

    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.

    What you have here isn't an implicit conversion from Foo to IGenereateSignature, it's a wrapper.

    As an explanation for this behaviour, you can't exect the compiler to go though every implementation of IGenerateSignature in scope to see whether it has an implicit conversion to/from Foo. What if there was more than one?


    In terms of how you can achieve this for Foo, Bar and Qux...

    What you're trying to achieve, one call to GetSignature(fooOrBarOrQux), isn't possible, because (based on your description of Foo) you can't have one variable that can be a Foo or a Bar or a Qux at compile time - they're unrelated. You'll always need three call sites, so there's no reason not to have three slightly-different conversions (wrapper class or overloaded method call or something) for the three cases.

    ... unless you use dynamic?