2

I have never done any extensive work with overloading operators, especially the implicit and explicit conversions.

However, I have several numeric parameters that are used frequently, so I am creating a struct as a wrapper around a numeric type to strongly type these parameters. Here's an example implementation:

public struct Parameter
{
    private Byte _value;
    public Byte Value { get { return _value; } }

    public Parameter(Byte value)
    {
        _value = value;
    }

    // other methods (GetHashCode, Equals, ToString, etc)

    public static implicit operator Byte(Parameter value)
    {
        return value._value;
    }
    public static implicit operator Parameter(Byte value)
    {
        return new Parameter(value);
    }

    public static explicit operator Int16(Parameter value)
    {
        return value._value;
    }
    public static explicit operator Parameter(Int16 value)
    {
        return new Parameter((Byte)value);
    }
}

As i was experimenting with my test implementation to get a hang of the explicit and implicit operators, I tried to explicitly cast a Int64 to my Parameter type and to my surprised it did not throw an exception, and even more surprising, it just truncated the number and moved on. I tried excluding the custom explicit operator and it still behaved the same.

public void TestCast()
{
    try
    {
        var i = 12000000146;
        var p = (Parameter)i;
        var d = (Double)p;

        Console.WriteLine(i);   //Writes 12000000146
        Console.WriteLine(p);   //Writes 146
        Console.WriteLine(d);   //Writes 146
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);  //Code not reached
    }
}

So I repeated my experiment with a plain Byte in place of my struct and has the same exact behavior, so obviously this is expected behavior, but I thought an explicit cast that results in a lose of data would throw an exception.

psubsee2003
  • 8,563
  • 8
  • 61
  • 79

2 Answers2

7

When the compiler is analyzing an explicit user-defined conversion it is allowed to put an explicit built-in conversion on "either side" (or both) of the conversion. So, for example, if you have a user-defined conversion from int to Fred, and you have:

int? x = whatever;
Fred f = (Fred)x;

then the compiler reasons "there is an explicit conversion from int to Fred, so I can make an explicit conversion from int? to int, and then convert int to Fred.

In your example, there is a built-in explicit conversion from long to short, and there is a user-defined explicit conversion from short to Parameter, so converting long to Parameter is legal.

The same is true of implicit conversions; the compiler may insert built-in implicit conversions on either side of a user-defined implicit conversion.

The compiler never chains two user defined conversions.

Building your own explicit conversions correctly is a difficult task in C#, and I encourage you to stop attempting to do so until you have a thorough and deep understanding of the entire chapter of the specification that covers conversions.

For some interesting aspects of chained conversions, see my articles on the subject:

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Thanks for the explanation and the links to your blog. I've missed some of the older posts, so this was very informative. The project I am working on is sort of a pet project that I am using as much to experiment with new aspects as I am using to help out in my real job, so the explicit/implicit conversions we as much an experiment as anything. – psubsee2003 Dec 16 '11 at 12:40
  • And now that I've read through your blog, I think I am understanding the traps of doing custom explicit and implicit conversions. From your answer, I get that there is an explicit conversion between long and byte, the compiler is inserting that conversion into the method call to convert from long to byte to Parameter. And since the cast from long to byte will take the least significant 8 bits from the long, I am left with 146 as the value assigned to Parameter. Makes complete sense, thank you. – psubsee2003 Dec 16 '11 at 12:57
3

This goal:

so I am creating a struct as a wrapper around a numeric type to strongly type these parameters

And this code:

public static implicit operator Byte(Parameter value)
{
    return value._value;
}
public static implicit operator Parameter(Byte value)
{
    return new Parameter(value);
}

Are in total contradiction. By adding 2-way implicit operators you annul any type-safety the wrapper might bring.

So drop the implicit conversions. You can change them to explicit ones.

H H
  • 263,252
  • 30
  • 330
  • 514
  • @Hank I wasn't clear enough enough in the description of the type-safety i was looking for. There are several different types of parameters (for sake of argument, Parameter1, Parameter2, etc). The type safety I was looking for is preventing someone from accidentally using Parameter1 in a method that is looking for Parameter2. But your point does make sense regardless, so I will rethink the implicit operator approach. – psubsee2003 Dec 16 '11 at 10:22