views:

71

answers:

2

Is there anything wrong with using an implicit operator like the following:

//linqpad c# program example
void Main()
{
    var testObject = new MyClass<int>() { Value = 1 };

    var add = 10 + testObject; //implicit conversion to int here
    add.Dump(); // 11
}

class MyClass<T>
{
    public T Value { get; set; }
    public static implicit operator T (MyClass<T> myClassToConvert)
    {
        return myClassToConvert.Value;
    }
}

I was thinking I could treat as instance of the object as a value type this way, but seeing as I've never seen an example of this I thought maybe there was a reason not to do something like this that someone could point out?

In my actual code I was thinking of doing this as part of a data abstraction layer, so that I could return objects with information describing the underlying data, but allow the logic code to treat it as a value type when all it needs to know about is the value, and at the same time keep it all nice and type safe with the generics.

+3  A: 

If all of the following are true:

  • all possible values of your MyClass<T> type (including null if it’s not a value type!) map to a valid value of T

  • the implicit operator never throws (not even for null!)

  • the implicit conversion makes semantic sense and is not confusing to the client programmer

then there is nothing wrong with this. Of course you could do any of these three things, but it would be bad design. In particular, an implicit operator that throws can be very hard to debug because the place where it is called doesn’t say that it is being called.

For example, consider that T? has no implicit conversion to T (where T is, of course, a value type). If there was such an implicit operator, it would have to throw when the T? is null, as there is no obvious value to convert null to that would make sense for any value type T.


Let me give an example where I had trouble debugging an issue where the implicit operator threw:

public string Foo()
{
    return some_condition ? GetSomething() : null;
}

Here, GetSomething returned something of a type I wrote which has a user-defined implicit conversion to string. I made absolutely sure that GetSomething could never return null, and yet I got a NullReferenceException! Why? Because the above code is not equivalent to

return some_condition ? (string)GetSomething() : (string)null;

but to

return (string)(some_condition ? GetSomething() : (Something)null);

Now you can see where the null came from!

Timwi
I'm not sure that I understand the concern around nullable types... It seems to me that if you had n = new MyClass<int?>(){Value=null}; and the consuming code attempted to do something like int i=n; and it would throw the same exception that any bad cast would, nothing special about the nullable type here right?
asawyer
@asawyer: It was just an example. Imagine there was no `T?` and *you* had to implement a `Nullable<T>` of your own. Would you give it an implicit conversion to `T`? My answer explains why you shouldn’t, and why the *real* `T?` doesn’t.
Timwi
I see now, it's these types of concerns that prompted the question in the first place. It looks like I should be ok to procede, but be very cautious.
asawyer
A: 

That's a great pattern. Just keep in mind that in order to use it as a variable of type T, you have to either explicitly cast it to T, or assign it to a variable of type T. The cast will take place automatically in method calls and other things (such as your addition example) that take a T.

http://stackoverflow.com/questions/3703555/implicit-conversion-without-assignment

arootbeer
That is not true.
Timwi
@Timwi, are you sure? `MyClass<T> myClass`, at this point, `myClass.*` will only resolve members on `MyClass<T>`, not on `T`. You would have to do as arootbeer mentions, and do `((T)myClass).*` in order to access any members on `T` itself.
Kirk Woll
I asked this very question just a few days ago. http://stackoverflow.com/questions/3703555/implicit-conversion-without-assignment
arootbeer
@Kirk: I know. *That* is true. But the statement in this answer is still wrong. You can use a `MyClass<T>` in a method call that expects a `T` parameter, for example. Or in a binary operator such as the example in the question. Or in a `return` or `yield return` statement. Or or or ...
Timwi
@Timwi - I've edited my statement to address your concerns with its correctness.
arootbeer
@Timwi, sure, arootbeer would have been better off phrasing it as, "you have to either explicitly cast it to `T` or use it where `T` is inferred, such as a variable or parameter of type `T`."
Kirk Woll
@arootbeet - You are correct, if the implicit operator does not run then you get the MyClass type, not T. This is not a problem for me, as in my actual impimentation the wrapper type exposes T as well as several additional related properties.
asawyer