Expression.Convert doesn't throw InvalidOperationException for invariant value type parameters?

只愿长相守 提交于 2019-12-07 04:19:13

问题


Expression.Convert generally throws in InvalidOperationException when "No conversion operator is defined between expression.Type and type."

The return type parameter of Func<> is covariant for reference types.

// This works.
Func<SomeType> a = () => new SomeType();
Func<object> b = a;

It isn't covariant for value types.

Variance applies only to reference types; if you specify a value type for a variant type parameter, that type parameter is invariant for the resulting constructed type.

// This doesn't work!
Func<int> five = () => 5;
Func<object> fiveCovariant = five;

However, Expression.Convert believes it is possible.

Func<int> answer = () => 42;
Expression answerExpression = Expression.Constant( answer );
// No InvalidOperationException is thrown at this line.
Expression converted 
    = Expression.Convert( answerExpression, typeof( Func<object> ) );

No InvalidOperationException is thrown when calling Expression.Convert. The expression tree compiles correctly, but when I call the created delegate, I get an expected InvalidCastException.

  1. Is this a bug? (I reported it as a bug on Microsoft Connect.)
  2. How to properly check whether a type can be converted to another type? Some answers seem to refer to using Convert. I would very much prefer a method which doesn't have to use exception handling as logic.

It seems the entire variance logic isn't properly supported. It correctly complains about not being able to convert from Func<SomeType> to Func<SomeOtherType>, but it doesn't complain about converting from Func<object> to Func<string>.

Interestingly, once SomeType and SomeOtherType are in the same class hierarchy (SomeOtherType extends from SomeType), it never throws the exception. If they aren't, it does.


回答1:


Is this a bug?

Yes. The expression tree library was probably not updated consistently throughout when we added covariance and contravariance. Sorry about that.

I reported it as a bug on Microsoft Connect.

Thanks! Someone will have a look at it then.

How to properly check whether a type can be converted to another type?

The question is vague. Given two type objects, do you want to know:

  • Does the .NET runtime think the types are assignment compatible?
  • Does the C# compiler think that there is an implicit conversion between the types?
  • Does the C# compiler think that there is an explicit conversion between the types?

"int" and "short" for example are not assignment compatible by .NET rules. Int is explicitly convertible but not implicitly convertible to short, and short is both implicitly and explicitly convertible to int, by C# rules.




回答2:


It's not a bug. Expression.Convert represents a run-time type check, so an InvalidCastException at run time would be the expected behavior.

Edit: that's not entirely correct. It doesn't exactly represent a run-time type check (Here's the documentation: http://msdn.microsoft.com/en-us/library/bb292051.aspx). However, the expression tree is created at run-time, so all the type checking must happen then.

Edit: I'm using .NET 4.0 too.

By the way, Convert doesn't complain about converting from Func<object> to Func<string> because that conversion is sometimes legal. It is legal if the Func<object> is a covariant reference to an object whose runtime type is Func<string>. Example:

Func<string> sFunc = () => "S";
Func<object> oFunc = sFunc;
Func<string> anotherSFunc = (Func<string>)oFunc;

Now, Convert decides whether to throw an InvalidOperationException by checking whether one type can be coerced to another. When checking delegates for a potential reference conversion, it looks like the code does check contravariant (argument) parameters and throws an InvalidOperationException if any is a value type. It doesn't seem to do that check for the covariant (return type) parameter. So I am beginning to suspect that this is a bug, though I'm inclined to reserve judgment on that until I have a chance to look at the spec (see Eric Lippert's Maybe there's something wrong with the universe, but probably not), which I don't have time to do right now.




回答3:


Eric Lippert answered part 1 of my question: it seems to be a bug. I started looking for a solution to question 2:

How to properly check whether a type can be converted to another type?

I just commit a first attempt at a Type.CanConvertTo( Type to ) method to my library. (Sources are way too complex to post here, sorry.)

if ( fromType.CanConvertTo( toType ) )
{
    convertedExpression = Expression.Convert( expression, toType );
}

So far it supports checking implicit conversions for:

  • Simple non-generic types.
  • Variance for nested generic interfaces and delegates.
  • Invariance for generic value type parameters.

It doesn't support:

  • Type constraints.
  • Custom implicit conversion operators.

It passes all my tests for implicit conversions. Although you can specify to include explicit conversions as well (and I actually use it like that at the moment), I still need to write unit tests for all those scenarios.



来源:https://stackoverflow.com/questions/7960018/expression-convert-doesnt-throw-invalidoperationexception-for-invariant-value-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!