20

How can I implement a struct so that the following cast can be performed?

var a = new StatusedValue<double>(1, false);
var b = (StatusedValue<int>)a;

My implementation should behave similarly to Nullable<T>, which works fine. However, this code fails with System.InvalidCastException:

public struct StatusedValue<T>  where T : struct
{
    public StatusedValue(T value) : this(value, true)
    {

    }

    public StatusedValue(T value, bool isValid)
    {
        this.value = value;
        this.isValid = isValid;
    }

    private T value;
    private bool isValid;

    public static implicit operator StatusedValue<T>(T value)
    {
        return new StatusedValue<T>(value);
    }

    public static explicit operator T(StatusedValue<T> value)
    {
        return value.value;
    }
}

Result:

Unable to cast object of type 'StatusedValue`1[System.Double]' to type 'StatusedValue`1[System.Int32]'.

Justin Morgan - On strike
  • 30,035
  • 12
  • 80
  • 104
Vladimir Sachek
  • 1,126
  • 1
  • 7
  • 20
  • Not really an answer, but maybe this (and the rest of that series) might be interesting for you: [Nullable micro-optimization, part four](http://ericlippert.com/2013/01/07/nullable-micro-optimization-part-four/) – Corak Sep 05 '14 at 11:54
  • 3
    Are you sure this code throws `InvalidCastException` ? I can't even compile this code. – Sriram Sakthivel Sep 05 '14 at 11:54
  • Sorry, original code was a bit different, but the point remains the same – Vladimir Sachek Sep 05 '14 at 12:03
  • 2
    Have a look at [this question](http://stackoverflow.com/questions/1025576/is-it-possible-in-c-sharp-to-overload-a-generic-cast-operator-in-the-following-w) with an answer from Jon Skeet and a comment from Eric Lippert (does it get better than that?!?) – petelids Sep 05 '14 at 12:03
  • What remains same? If original code is different you need to post code which mocks the original, not different one. If you post different code, answers also will be different. – Sriram Sakthivel Sep 05 '14 at 12:06
  • @SriramSakthivel, the question how to implement a struct to make the cast compilable and without throwing an exception. – Vladimir Sachek Sep 05 '14 at 12:19
  • To be honest, I wouldn't want to replace compile-time type-checking with run-time type-checking, so I'd just do `var b = new StatusedValue((int)a.Value, false); ` – Matthew Watson Sep 05 '14 at 12:19
  • This question doesn't really have anything to do with `Nullable`. – Justin Morgan - On strike Sep 05 '14 at 18:41
  • @JustinMorgan Nullable was chosen as a base implementation, because it works in desirable way. – Vladimir Sachek Sep 05 '14 at 19:49

6 Answers6

17

This works for Nullable<T> types because they get special treatment from the compiler. This is called a "lifted conversion operator", and you cannot define your own.

From section 6.4.2 of the C# Specification:

6.4.2 Lifted conversion operators

Given a user-defined conversion operator that converts from a non-nullable value type S to a non-nullable value type T, a lifted conversion operator exists that converts from S? to T?. This lifted conversion operator performs an unwrapping from S? to S followed by the user-defined conversion from S to T followed by a wrapping from T to T?, except that a null valued S? converts directly to a null valued T?. A lifted conversion operator has the same implicit or explicit classification as its underlying user-defined conversion operator. The term “user-defined conversion” applies to the use of both user-defined and lifted conversion operators

Community
  • 1
  • 1
dcastro
  • 66,540
  • 21
  • 145
  • 155
6

If you're happy calling a method, try

public StatusedValue<U> CastValue<U>() where U : struct
{
    return new StatusedValue<U>((U)Convert.ChangeType(value, typeof(U)), isValid);
}

This will unfortunately throw at runtime rather than compile time if T cannot be converted to U.

Edit: As pointed out below, if you constrain to IConvertible as well as/instead of struct then every conversion is theoretically possible at compile time, and you'll only get a runtime failure because of bad runtime values.

Rawling
  • 49,248
  • 7
  • 89
  • 127
  • Not sure but I think this will throw if T does not implement IConvertible even if an implicit or explicit cast exists between T and U. The good news is that you can constraint T to IConvertible – InBetween Sep 05 '14 at 12:11
  • @InBetween Damn, I think you're right. I'd hoped it would at least try a runtime cast if given two reference types. Sounds like OP might only want to do it between the basic types anyway though. – Rawling Sep 05 '14 at 12:14
  • Well it's already constrained to structs, so that's not so bad. – Matthew Watson Sep 05 '14 at 12:15
3

Nullables are specially handled by the compiler, I don't know if this is the case here.

Your operators would allow this:

StatusedValue<int> b = (int)a;

which is probably not what you want, because IsValid is not copied this way.

You could implement an extension method like this:

 public static StatusedValue<TTarget> Cast<TSource, TTarget>(this StatusedValue<TSource> source)
    where TTarget : struct
    where TSource : struct
  {
    return new StatusedValue<TTarget>(
      (TTarget)Convert.ChangeType(
        source.Value, 
        typeof(TTarget)),
      source.IsValid);
  }


  b = a.Cast<int>();

But the compiler cannot check if the types are compatible. ChangeType also returns an object, thus boxing your value.

Stefan Steinegger
  • 63,782
  • 15
  • 129
  • 193
  • 2
    This won't build as the compiler doesn't know how to convert between `TSource` and `TTarget`. (Also because `Value` and `IsValid` don't exist...) And why an extension method when it's his own class? – Rawling Sep 05 '14 at 12:05
  • I think you're right. Would need to cast (and box) to object before. Or better do a Convert. However, it was just an idea. – Stefan Steinegger Sep 05 '14 at 15:25
1

If you don't need a casting, you can add a method like this:

    public StatusedValue<int> ConvertToStatusedInt() {
        return new StatusedValue<int>(Convert.ToInt32(value), isValid);
    }

As suggested in comment:

    public StatusedValue<Q> ConvertTo<Q>() where Q:struct {
        return new StatusedValue<Q>((Q)Convert.ChangeType(value, typeof(Q)), isValid);
    }
Blau
  • 5,742
  • 1
  • 18
  • 27
1

For a workaround, you will need to provide a way for converting from one underlying type to the other, since the compiler won't be able to figure that out:

public StatusedValue<TResult> To<TResult>(Func<T, TResult> convertFunc)
where TResult : struct {
   return new StatusedValue<TResult>(convertFunc(value), isValid);
}

You can then do:

var a = new StatusedValue<double>(1, false);
var b = a.To(Convert.ToInt32);

With some reflection you could build a lookup table of the Convert methods, and lookup the right one based on the type arguments, and then you could default the conversion function to null and if it's not provided, try to lookup the correct conversion automatically. This would remove the clumsy Convert.ToInt32 part, and simply do var b = a.To<int>();

As Rawling points out, Convert.ChangeType can be used. This would make my method look like:

public StatusedValue<T2> To<T2>(Func<T, T2> convertFunc = null)
where T2 : struct {
   return new StatusedValue<T2>(
      convertFunc == null
         ? (T2)Convert.ChangeType(value, typeof(T2))
         : convertFunc(value),
      isValid
   );
}
Dave Cousineau
  • 12,154
  • 8
  • 64
  • 80
  • `With some reflection you could build a lookup table of the Convert methods` It's called `Convert.ChangeType` – Rawling Sep 05 '14 at 12:25
1

The answer to why it's like this has already been posted and marked as the answer.

However, you can simplify the syntax to make it easier and clearer to do this while retaining compile-time type-safety.

Firstly, write a helper class to avoid having to specify redundant type parameters:

public static class StatusedValue
{
    public static StatusedValue<T> Create<T>(T value, bool isValid = true) where T: struct
    {
        return new StatusedValue<T>(value, isValid);
    }
}

Next you need to expose the underlying value with a Value property (otherwise you can't cast it from code).

Finally you can change your original code from this:

var a = new StatusedValue<double>(1, false);
var b = (StatusedValue<int>)a;

To this:

var a = StatusedValue.Create(1.0, false);
var b = StatusedValue.Create((int)a.Value, false); 

where you are doing a simple cast on a.Value.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • He already has a constructor that does what your `Create` does. – Gutblender Sep 10 '14 at 18:28
  • @Gutblender But that constructor requires you to specify type arguments (i.e. you must put the type in ``) whereas the static helper class allows the compiler to infer the type from the argument type. That's why I said "Firstly, write a helper class to avoid having to specify redundant type parameters" – Matthew Watson Sep 11 '14 at 06:15
  • True. I did notice the syntax is a bit sweeter. But the question becomes, can OP get away with a non-generic class that holds a `ValueType` instead? I have made a structure pretty much exactly like this for one purpose I had to describe mandatory and optional features. It works just fine for that. – Gutblender Sep 11 '14 at 13:37