3

I have a class (simplified for the purposes of this question) that wraps a decimal value and uses a couple of implicit operator declarations for converting between the type and the wrapped value:

private class DecimalWrapper
{
    public decimal Value { get; private set; }

    public DecimalWrapper(decimal value)
    {
        Value = value;
    }

    public static implicit operator decimal(DecimalWrapper a)
    {
        return a != null ? a.Value : default(decimal);
    }

    public static implicit operator DecimalWrapper(decimal value)
    {
        return new DecimalWrapper(value);
    }
}

The usages of implicit operator here allow things like these to be done:

DecimalWrapper d1 = 5; // uses implicit operator DecimalWrapper
DecimalWrapper d2 = 10;
var result = d1 * d2; // uses implicit operator decimal

Assert.IsTrue(result.Equals(50));
Assert.IsTrue(result == 50);

Now, consider a second class (again, simplified) that has an overloaded constructor that can take a decimal or a DecimalWrapper:

private class Total
{
    private readonly DecimalWrapper _total;

    public Total(DecimalWrapper total)
    {
        _total = total;
    }

    public Total(decimal totalValue)
    {
        _total = totalValue;
    }
}

I would expect to be able to instantiate an instance of Total by passing in an integer value, which would get converted to a decimal:

var total = new Total(5);

However, this results in a compiler error:

The call is ambiguous between the following methods or properties: 'Namespace.Total.Total(TypeTests.DecimalWrapper)' and 'Namespace.Total.Total(decimal)'

To fix this, you have to remove the implicit operator decimal or specify that the value 5 is in fact a decimal:

var total = new Total(5m);

This is all well and good, but I don't see why the implicit operator decimal is relevant here. So, what is going on?

5
  • "does not even get executed when this runs" --- why should it? You have passed a decimal, so no cast needed - the Total(decimal totalValue) constructor exactly matches it. Commented Nov 15, 2016 at 0:21
  • @zerkms - I've clarified my question a little Commented Nov 15, 2016 at 0:25
  • 1
    It is relevant because there is Total(DecimalWrapper total) constructor and an implicit conversion int -> decimal -> DecimalWrapper. Compiler (after it could not find the exact signature match) checks what constructors it can fulfill after type conversion, and in this case it's both. Commented Nov 15, 2016 at 0:26
  • 1
    You've got implicit operators because you want to avoid situations like multiple constructors: why not just remove the one that explicitly takes a DecimalWrapper? Commented Nov 15, 2016 at 0:31
  • Btw, relevant: msdn.microsoft.com/en-us/library/aa691338(v=vs.71).aspx (in the newer downloadable standard version it has more thorough description, but even by that link it's fine) Commented Nov 15, 2016 at 0:33

1 Answer 1

3

Are you looking for a citation from the language specification?

The cause of this has to do with overload resolution. When you specify an int value as the constructor parameter, no overload is considered "best" because both require a conversion. The specification doesn't consider two levels of conversion different from one level, so the two constructor overloads are equivalent to each other.

As Blorgbeard noted in the comments, you can easily resolve the issue by getting rid of one of the constructors. He suggests removing the DecimalWrapper overload, but since your field is of the DecimalWrapper type, I'd get rid of the decimal overload instead. Doing it this way, if you specify an int value for the constructor, the compiler will implicitly convert to decimal and then DecimalWrapper for you. If you specify a decimal value for the constructor, the compiler will implicity convert to DecimalWrapper for that call, which is what your decimal constructor would have done anyway.

Of course, yet another way to address the issue would be to add other constructors to the Total class, e.g. one that takes an int. Then no conversion is required and the int constructor would be chosen. But this seems like overkill to me.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.