无效的强制转换异常泛型

13
我正在遇到一个问题,我正在使用反射从类中获取属性,但问题是反射将它们返回为对象,我无法将其转换为实际类型。
例如,如果这是该类:
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; }
}

将一个类型为 Row<bool> 的对象转换为 Row<object> 是可行的:
    var a = new Row<bool>
    {
        Name = "Foo",
        Value = true
    };

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

但是当我尝试从 object 转换到 Row<object> 时,它似乎忽略了我的显式运算符并抛出了一个 System.InvalidCastException 异常。
var c = (object) a; // Simulate getting from reflection

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

我错过了什么?

请按照以下方式进行编程:var c = (Row<object>) a; - Ehsan
1
不确定你的期望是什么 - 在你的示例中没有 explicit operator Row<object>(object o) - Alexei Levenkov
这可能是因为显式操作符期望一个 Row<T>,而你却给了它一个 object - Adam Houldsworth
@AlexeiLevenkov 是的,那很清楚,但问题是 - 即使他定义了explicit operator Row<object>(object o),他会如何定义它? - JLRishe
@JLRishe 你不能为 object 定义一个显式转换。 - Adam Houldsworth
显示剩余3条评论
3个回答

6

使用 dynamic 而不是 object 可以强制进行运行时实际类型检查:

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

它将调用您的 Row<T> -> Row<object> 强制转换运算符。

1
你不能声明 explicit operator Row<object>(object o) - Adam Houldsworth
Visual Studio 抱怨了...所以我应该先将反射返回的对象转换为动态类型,然后再转换为我的类型? - Phil
1
@Phil 是的,这就是Marcin所建议的。你可以这样做:var d = (Row<object>)(dynamic)c; - JLRishe
5
我正在提出正式投诉,因为这个回答所做的只是“让问题消失”。它没有试图解释出了什么问题,也没有理解 OP 试图实现什么目标或者建议更好的方法来处理事情。 - Jon

4
问题在于类型转换时,只有在要转换的值的静态类型中定义了一个转换运算符时,才会查找该运算符。在您的示例中,c 的静态类型是 object,而 object 既不继承自 Row<object>,也没有到该类型的转换运算符,因此导致了运行时异常。
看起来这个问题可以通过更好的设计轻松解决。
您希望将任何类型的 Row<T> 视为 Row<object>,而转换运算符仅仅是绕过这些类型之间不具有层次关系的事实。那么为什么不使它们相关联,从而避免出现问题呢?
例如:
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; }
    }
}

这似乎可以满足您的需求:

  • 转换问题得以解决,因为您现在可以将任何类型的Row<T>转换为基类Row(接管了您最初设计中Row<object>的职责),并轻松访问NameValue,无论Value的类型是什么。
  • Row.Value的setter是受保护的,因此您不能从外部将Row<int>转换为Row并使Value成为例如string,从而保持类型安全性。

哇,现在只是测试这个设计,最初看起来似乎确实解决了根本原因(不太好的设计)。需要订购Jon Skeet的书... - Phil

2
您可以通过反射来实现这一点:
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)
        };
    }
}

您需要使用以下方式调用此函数:
var d = RowHelper.LoadRow(c);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接