【问题标题】:How to check assignability of types at runtime in C#?如何在 C# 运行时检查类型的可分配性?
【发布时间】:2011-04-07 01:09:11
【问题描述】:

Type 类有一个方法IsAssignableFrom() 几乎可以工作。不幸的是,只有当这两种类型相同或第一种在第二种的层次结构中时,它才会返回 true。它说decimal 不能从int 分配,但我想要一种方法来表明decimals 可以从ints 分配,但ints 并不总是可以从decimal 分配s。编译器知道这一点,但我需要在运行时弄清楚这一点。

这是一个扩展方法的测试。

[Test]
public void DecimalsShouldReallyBeAssignableFromInts()
{
    Assert.IsTrue(typeof(decimal).IsReallyAssignableFrom(typeof(int)));
    Assert.IsFalse(typeof(int).IsReallyAssignableFrom(typeof(decimal)));
}

有没有办法实现IsReallyAssignableFrom(),它可以像IsAssignableFrom() 一样工作,但也可以通过上面的测试用例?

谢谢!

编辑:

这基本上就是它的使用方式。此示例无法为我编译,因此我必须将 Number 设置为 0(而不是 0.0M)。

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter)]
public class MyAttribute : Attribute
{
    public object Default { get; set; }
}

public class MyClass
{
    public MyClass([MyAttribute(Default= 0.0M)] decimal number)
    {
        Console.WriteLine(number);
    }
}

我收到此错误:

错误4 属性参数必须是属性参数类型的常量表达式、typeof表达式或数组创建表达式

【问题讨论】:

  • 你会如何处理这些信息?
  • 这是框架代码(用于序列化对象)。我有一个要序列化的十进制对象,我想将其默认为 0。不能为其分配 0.0M(编译时失败:不是常量表达式)。想要使用 int 0 并且需要一种方法来验证这是安全的,但不允许使用 DateTime 来默认小数。
  • 你能显示不能为你编译的代码吗? const decimal D = 0.0M 在这里编译得很好。
  • @Pavel Minaev:我已经编辑了我的问题以包含一个不适合我编译的示例。
  • 啊,是的,属性参数。就 CLR 而言,十进制并不是真正的原始类型,因此在 CLR 级别上不存在十进制文字。对于常量字段,C# 通过使用原始字节来解决这个问题,但它不是属性的选项。从好的方面来说,我相信这只是小数的问题(因为只有它们在 C# 中没有相当文字的文字)。您可以做的是要求类型与除十进制以外的所有内容完全匹配,并专门检查后者的“兼容”原始类型(您事先知道)。

标签: c# types


【解决方案1】:

实际上有三种方式可以将一个类型“分配”给您正在寻找的另一个类型。

  • 类层次结构、接口实现、协变和逆变。这是.IsAssignableFrom 已经检查的内容。 (这还包括允许的装箱操作,例如 intobjectDateTimeValueType。)

  • 用户定义的隐式转换。这是所有其他答案所指的。您可以通过反射检索这些,例如从intdecimal 的隐式转换是一个静态方法,如下所示:

    System.Decimal op_Implicit(Int32)
    

    您只需要检查两个相关类型(在本例中为Int32Decimal);如果转换不在那些,那么它不存在。

  • C# language specification 中定义的内置隐式转换。不幸的是,反射没有显示这些。您必须在规范中找到它们并将可分配性规则手动复制到您的代码中。这包括数字转换,例如intlong 以及 floatdouble、指针转换、可空转换(intint?)和 lifted conversions

此外,用户定义的隐式转换可以与内置的隐式转换链接。例如,如果存在从int 到某种类型T 的用户定义的隐式转换,那么它还兼作从shortT 的转换。类似地,Tshort 兼作 Tint

【讨论】:

  • 感谢 Timwi 和其他响应者。确实,我无法获得内置转换,但我可以忍受。这是我最终得到的方法的实现。 public static bool IsReallyAssignableFrom(this Type t, Type other) { if (t.IsAssignableFrom(other)) { return true; } return t.GetMethod("op_Implicit", new[] {other}) != null; }
  • @epicsmile:您还需要检查 other 类型上的op_Implicit,对于那个,查看它的返回类型而不是它的参数类型。
  • 您省略了指针转换。您还省略了提升和可为空的转换。此外,即使撇开提升不谈,用户定义的隐式转换也比您的简短草图所允许的要复杂得多。用户定义的从 int 到 Foo 的隐式转换也可以用作用户定义的从 short 到 Foo 的隐式转换,因为 short 转到 int 而 int 转到 Foo。
  • 即使您进行了更新,事情仍然比您想象的要复杂得多。考虑从 struct S 到十进制的转换?当有来自 S 的用户定义转换时?诠释。 -- 这被分析为从 S 到 S? 的可空隐式转换,然后是从 S? 的用户定义转换到 int,然后是从 int 到 int? 的可空转换,然后是从 int? 提升的数字转换?到十进制?还是从整数到十进制,然后从十进制到十进制? ?让你想知道,不是吗? C# 中的转换逻辑棘手;没有简单的方法让它正确。
  • @Eric Lippert:您为什么不发布自己的更完整的答案?我会赞成的。
【解决方案2】:

这个几乎可以工作...它使用的是 Linq 表达式:

public static bool IsReallyAssignableFrom(this Type type, Type otherType)
{
    if (type.IsAssignableFrom(otherType))
        return true;

    try
    {
        var v = Expression.Variable(otherType);
        var expr = Expression.Convert(v, type);
        return expr.Method == null || expr.Method.Name == "op_Implicit";
    }
    catch(InvalidOperationException ex)
    {
        return false;
    }
}

唯一不起作用的情况是基本类型的内置转换:对于应该显式的转换(例如,intshort),它错误地返回 true。我想你可以手动处理这些情况,因为它们的数量是有限的(而且相当少)。

我真的不喜欢必须捕获异常来检测无效转换,但我没有看到任何其他简单的方法来做到这一点...

【讨论】:

    【解决方案3】:

    Timwi 的回答真的很完整,但我觉得有一种更更简单的方式可以让你获得相同的语义(检查“真实”可分配性),而无需实际定义自己这是什么。

    您可以尝试相关作业并查找InvalidCastException(我知道这很明显)。这样,您 避免了检查 Timwi 提到的可分配性的三种可能含义的麻烦。这是一个使用 xUnit 的示例:

    [Fact]
    public void DecimalsShouldReallyBeAssignableFromInts()
    {
        var d = default(decimal);
        var i = default(i);
    
        Assert.Throws<InvalidCastException)( () => (int)d);
        Assert.DoesNotThrow( () => (decimal)i);
    }
    

    【讨论】:

    • 我想知道分配是否适用于该类型的任何值,而不仅仅是默认值。您可以将一些(甚至很多)十进制值转换为 int,但不是全部。不幸的是,默认十进制值是您可以转换的值之一。
    • 为什么你认为(int)d 会抛出InvalidCastException,为什么你认为后者甚至与可分配性有关?
    • 错误答案。这仅检查是否存在 explicit 转换或强制转换,而不是 implicit (这通常是 assignability 的含义)。此外,它不适用于需要处理 Type 对象的代码;仅当您在编译时知道确切的类型时,它才有效。最后,正如 epismile 指出的那样,这取决于您要转换的实际值(例如,当且仅当您传入的对象实际上是一个装箱的 int 时,它才会让您将 object 转换为 int)。
    【解决方案4】:

    您正在寻找的是是否存在从一种类型到另一种类型的隐式转换。我认为这可以通过反射实现,尽管它可能很棘手,因为隐式强制转换应该定义为运算符重载,它是一种静态方法,我认为它可以在任何类中定义,而不仅仅是可以隐式转换的类。

    【讨论】:

    • 不是任何类。实际上,该集合仅限于两个条目。
    • 不想听起来太挑剔,但intdecimal隐式转换,而不是隐式转换
    【解决方案5】:

    为了确定一种类型是否可以分配给另一种类型,您必须寻找从一种类型到另一种类型的隐式转换。您可以通过反射来做到这一点。

    正如 Timwi 所说,您还必须了解一些内置规则,但这些规则可以是硬编码的。

    【讨论】:

    • 你可以从:typeof(decimal).GetMethod("op_Implicit", new Type[] { typeof(int) })开始
    • 不幸的是,这找不到从 intlong 的隐式转换(以及大多数其他)。
    • 并不完全有帮助...因为它只是重复 timwi 所说的话。
    • Johannes:既然我的帖子是第一篇,那么 Timwi 不是真的只是在扩展我所说的内容吗?
    【解决方案6】:

    实际上恰好是decimal 类型不能“分配”给int 类型,反之亦然。涉及装箱/拆箱时会出现问题。

    举个例子:

    int p = 0;
    decimal d = 0m;
    object o = d;
    object x = p;
    
    // ok
    int a = (int)d;
    
    // invalid cast exception
    int i = (int)o;
    
    // invalid cast exception
    decimal y = (decimal)p;
    
    // compile error
    int j = d;
    

    这段代码看起来应该可以工作,但是从 object 进行类型转换会产生无效转换异常,最后一行会产生编译时错误。

    a 的赋值起作用的原因是decimal 类对int 的类型转换运算符具有显式覆盖。不存在从 decimalint 的隐式类型转换运算符。

    编辑: 甚至不存在反向的隐式运算符。 Int32 实现了 IConvertible,这就是它转换为十进制的方式 结束编辑

    换句话说,类型不是可赋值的,而是可转换的

    您可以扫描程序集以查找显式类型转换运算符和 IConvertible 接口,但我得到的印象是,对于您知道会遇到的特定少数情况进行编程,这对您没有帮助。

    祝你好运!

    【讨论】:

      猜你喜欢
      • 2012-01-27
      • 2020-06-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-02
      • 2014-06-02
      • 1970-01-01
      相关资源
      最近更新 更多