Richard Ev
Richard Ev

Reputation: 54117

Implicit operators and a compiler error

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?

Upvotes: 3

Views: 87

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70671

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.

Upvotes: 3

Related Questions