简体   繁体   中英

Invalid cast exception generics

I'm having this issue, I'm using reflection to pull properties from a class but the problem is reflection returns them as an object and I can't get it into my actual type.

Take for example, if this is the class:

public class Row<T>
{
    public static explicit operator Row<object>(Row<T> o)
    {
        return new Row<object>
        {
            Name = o.Name,
            Value = o.Value
        };
    }

    public string Name { get; set; }

    public T Value { get; set; }
}

Casting from one say Row<bool> to Row<object> works:

    var a = new Row<bool>
    {
        Name = "Foo",
        Value = true
    };

    var b = (Row<object>)a; // Works

But when I try to go from object to Row<object> it seems to ignore my explicit operator and throw a System.InvalidCastException:

var c = (object) a; // Simulate getting from reflection

var d = (Row<object>) c; // System.InvalidCastException

What am I missing?

Use dynamic instead of object to force runtime real type check:

var c = (dynamic)a;
var d = (Row<object>)c; // Works fine

It will call your Row<T> -> Row<object> cast operator.

The problem here is that casting does not look for a conversion operator unless one is defined on the static type of the value you are trying to cast. In your example the static type of c is object and object neither derives from nor has a conversion operator to Row<object> , resulting in the runtime exception.

It looks like this problem can be easily sidestepped with a better design.

You want to treat any type of Row<T> as a Row<object> and the conversion operator does nothing more than work around the fact that these types are not hierarchically related. So why not make them related and avoid the problem in the first place?

For example:

public abstract class Row
{
    public string Name { get; set; }

    public object Value { get; protected set; }
}

public class Row<T> : Row
{
    public new T Value
    { 
        get { return (T)base.Value; }
        set { base.Value = value; }
    }
}

This seems to do what you want:

  • The casting problem is solved because you can now cast any type of Row<T> to the base class Row (which takes over the responsibilities of Row<object> in your initial design) and easily access Name and Value no matter what type the Value is.
  • The Row.Value setter is protected so you cannot cast a Row<int> to Row and make Value eg a string from outside, maintaining type safety.

You can accomplish this with reflection:

public class RowHelper
{
    public static Row<object> LoadRow(object o)
    {
        var type = o.GetType();
        return new Row<object>
        {
            Name = (string)type.InvokeMember("Name", BindingFlags.GetProperty, null, o, null),
            Value = type.InvokeMember("Value", BindingFlags.GetProperty, null, o, null)
        };
    }
}

You would call this with:

var d = RowHelper.LoadRow(c);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM