简体   繁体   English

如何在 C# 中在运行时检查类型的可分配性?

[英]How to check assignability of types at runtime in C#?

The Type class has a method IsAssignableFrom() that almost works. Type类有一个几乎有效的方法IsAssignableFrom() Unfortunately it only returns true if the two types are the same or the first is in the hierarchy of the second.不幸的是,它仅在两种类型相同或第一种类型在第二种类型的层次结构中时才返回 true。 It says that decimal is not assignable from int , but I'd like a method that would indicate that decimal s are assignable from int s, but int s are not always assignable from decimal s.它说decimal不能从int分配,但我想要一种方法可以表明decimal s 可以从int分配,但int s 并不总是可以从decimal s 分配。 The compiler knows this but I need to figure this out at runtime.编译器知道这一点,但我需要在运行时弄清楚这一点。

Here's a test for an extension method.这是一个扩展方法的测试。

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

Is there a way to implement IsReallyAssignableFrom() that would work like IsAssignableFrom() but also passes the test case above?有没有办法实现IsReallyAssignableFrom() ,它可以像IsAssignableFrom()一样工作但也通过上面的测试用例?

Thanks!谢谢!

Edit:编辑:

This is basically the way it would be used.这基本上就是它的使用方式。 This example does not compile for me, so I had to set Number to be 0 (instead of 0.0M ).这个例子不适合我编译,所以我必须将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);
    }
}

I get this error:我收到此错误:

Error 4 An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type错误 4 属性参数必须是属性参数类型的常量表达式、typeof 表达式或数组创建表达式

There are actually three ways that a type can be “assignable” to another in the sense that you are looking for.实际上,在您正在寻找的意义上,可以通过三种方式将一个类型“分配”给另一个类型。

  • Class hierarchy, interface implementation, covariance and contravariance.类层次结构、接口实现、协变和逆变。 This is what .IsAssignableFrom already checks for.这是.IsAssignableFrom已经检查的内容。 (This also includes permissible boxing operations, eg int to object or DateTime to ValueType .) (这也包括允许的装箱操作,例如intobjectDateTimeValueType 。)

  • User-defined implicit conversions.用户定义的隐式转换。 This is what all the other answers are referring to.这就是所有其他答案所指的内容。 You can retrieve these via Reflection, for example the implicit conversion from int to decimal is a static method that looks like this:您可以通过反射检索这些,例如从intdecimal的隐式转换是一个静态方法,如下所示:

     System.Decimal op_Implicit(Int32)

    You only need to check the two relevant types (in this case, Int32 and Decimal );您只需要检查两个相关类型(在本例中为Int32Decimal ); if the conversion is not in those, then it doesn't exist.如果转换不在那些中,则它不存在。

  • Built-in implicit conversions which are defined in the C# language specification . C# 语言规范中定义的内置隐式转换。 Unfortunately Reflection doesn't show these.不幸的是,Reflection 没有显示这些。 You will have to find them in the specification and copy the assignability rules into your code manually.您必须在规范中找到它们并手动将可分配性规则复制到您的代码中。 This includes numeric conversions, eg int to long as well as float to double , pointer conversions, nullable conversions ( int to int? ), and lifted conversions .这包括数字转换,例如intlong以及floatdouble 、指针转换、可为空的转换( intint? )和提升转换

Furthermore, a user-defined implicit conversion can be chained with a built-in implicit conversion.此外,用户定义的隐式转换可以与内置的隐式转换链接。 For example, if a user-defined implicit conversion exists from int to some type T , then it also doubles as a conversion from short to T .例如,如果存在从int到某种类型T的用户定义的隐式转换,那么它也兼作从shortT的转换。 Similarly, T to short doubles as T to int .类似地, T to shortT to int

This one almost works... it's using Linq expressions:这个几乎有效......它使用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;
    }
}

The only case that doesn't work is for built-in conversions for primitive types: it incorrectly returns true for conversions that should be explicit (eg int to short ).唯一不起作用的情况是原始类型的内置转换:对于应该是显式的转换(例如intshort ),它错误地返回true I guess you could handle those cases manually, as there is a finite (and rather small) number of them.我想您可以手动处理这些情况,因为它们的数量有限(而且相当少)。

I don't really like having to catch an exception to detect invalid conversions, but I don't see any other simple way to do it...我真的不喜欢捕捉异常来检测无效转换,但我没有看到任何其他简单的方法来做到这一点......

Timwi's answer is really complete, but I feel there's an even simpler way that would get you the same semantics (check "real" assignability), without actually defining yourself what this is. Timwi 的回答非常完整,但我觉得有一种更简单的方法可以让您获得相同的语义(检查“真实”可分配性),而无需实际定义自己这是什么。

You can just try the assignment in question and look for an InvalidCastException (I know it's obvious).您可以尝试有问题的分配并查找InvalidCastException (我知道这很明显)。 This way you avoid the hassle of checking the three possible meanings of assignability as Timwi mentioned.这样您就可以避免像 Timwi 提到的那样检查可分配性的三种可能含义的麻烦 Here's a sample using xUnit:这是使用 xUnit 的示例:

[Fact]
public void DecimalsShouldReallyBeAssignableFromInts()
{
    var d = default(decimal);
    var i = default(i);

    Assert.Throws<InvalidCastException)( () => (int)d);
    Assert.DoesNotThrow( () => (decimal)i);
}

In order to find out if one type can be assigned to another, you have to look for implicit conversions from one to the other.为了确定一种类型是否可以分配给另一种类型,您必须寻找从一种类型到另一种类型的隐式转换。 You can do this with reflection.你可以通过反射来做到这一点。

As Timwi said, you will also have to know some built-in rules, but those can be hard-coded.正如 Timwi 所说,您还必须了解一些内置规则,但这些规则可以是硬编码的。

What you are looking for is if there's an implicit cast from the one type to the other.您正在寻找的是是否存在从一种类型到另一种类型的隐式转换。 I would think that's doable by reflection, though it might be tricky because the implicit cast should be defined as an operator overload which is a static method and I think it could be defined in any class, not just the one that can be implicitly converted.我认为这可以通过反射来实现,尽管它可能很棘手,因为隐式强制转换应该定义为运算符重载,它是一种静态方法,我认为它可以在任何类中定义,而不仅仅是可以隐式转换的类。

It actually happens to be the case that the decimal type is not "assignable" to the int type, and vice versa.实际上, decimal类型不能“分配”给int类型,反之亦然。 Problems occur when boxing/unboxing gets involved.当涉及装箱/拆箱时会出现问题。

Take the example below:以下面的例子为例:

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;

This code looks like it should work, but the type cast from object produces an invalid cast exception, and the last line generates a compile-time error.这段代码看起来应该可以工作,但是从 object 类型转换产生了一个无效的转换异常,最后一行产生了一个编译时错误。

The reason the assignment to a works is because the decimal class has an explicit override on the type cast operator to int .分配给a的原因是因为decimal类对类型转换运算符显式覆盖为int There does not exist an implicit type cast operator from decimal to int .不存在从decimalint的隐式类型转换运算符。

Edit: There does not exist even the implicit operator in reverse.编辑:甚至不存在反向隐式运算符。 Int32 implements IConvertible, and that is how it converts to decimal End Edit Int32 实现了 IConvertible,这就是它如何转换为十进制End Edit

In other words, the types are not assignable , but convertible .换句话说,类型不是assignable ,而是convertible

You could scan assemblies for explicit type cast operators and IConvertible interfaces, but I get the impression that would not serve you as well as programming for the specific few cases you know you will encounter.您可以扫描程序集以查找显式类型转换运算符和 IConvertible 接口,但我的印象是,对于您知道会遇到的特定少数情况,这不会为您提供服务以及编程。

Good luck!祝你好运!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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