如何对 LINQ/lambdas/表达式树中的值类型进行编译时检查?
我使用以下代码以线程安全的方式设置 Control
属性:
private delegate void SetPropertyThreadSafeDelegate<TPropertyType>(Control @this, Expression<Func<TPropertyType>> property, TPropertyType value);
public static void SetPropertyThreadSafe<TPropertyType>(this Control @this, Expression<Func<TPropertyType>> property, TPropertyType value)
{
var propertyInfo = (property.Body as MemberExpression ?? (property.Body as UnaryExpression).Operand as MemberExpression).Member as PropertyInfo;
if (propertyInfo == null ||
!propertyInfo.ReflectedType.IsAssignableFrom(@this.GetType()) ||
@this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (propertyInfo.PropertyType.IsValueType &&
!propertyInfo.PropertyType.IsAssignableFrom(typeof(TPropertyType)))
{
throw new ArgumentException(string.Format("Attempted to assign incompatible value type: expecting {0}, got {1}.", propertyInfo.PropertyType, typeof(TPropertyType)));
}
if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TPropertyType>(SetPropertyThreadSafe), new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value });
}
}
它的调用方式如下:
downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, 32);
这样做的原因是为了在编译时检查属性名称和类型分配。它非常适合标准对象,但对于值类型来说一切都有点梨形,因为编译器很乐意接受以下内容,这当然会在运行时爆炸:
downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, 'c');
downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, long.MaxValue);
我已经修改了 SetPropertyThreadSafe
方法处理使用值类型的情况,如果使用不正确的类型作为参数,则抛出异常,但我真正寻找的是能够让此方法对 100% 执行编译时类型检查案例,即对象和值类型。这是否可能,如果可以,我需要如何修改我的代码才能做到这一点?
I'm using the following code to set Control
properties in a thread-safe manner:
private delegate void SetPropertyThreadSafeDelegate<TPropertyType>(Control @this, Expression<Func<TPropertyType>> property, TPropertyType value);
public static void SetPropertyThreadSafe<TPropertyType>(this Control @this, Expression<Func<TPropertyType>> property, TPropertyType value)
{
var propertyInfo = (property.Body as MemberExpression ?? (property.Body as UnaryExpression).Operand as MemberExpression).Member as PropertyInfo;
if (propertyInfo == null ||
!propertyInfo.ReflectedType.IsAssignableFrom(@this.GetType()) ||
@this.GetType().GetProperty(propertyInfo.Name, propertyInfo.PropertyType) == null)
{
throw new ArgumentException("The lambda expression 'property' must reference a valid property on this Control.");
}
if (propertyInfo.PropertyType.IsValueType &&
!propertyInfo.PropertyType.IsAssignableFrom(typeof(TPropertyType)))
{
throw new ArgumentException(string.Format("Attempted to assign incompatible value type: expecting {0}, got {1}.", propertyInfo.PropertyType, typeof(TPropertyType)));
}
if (@this.InvokeRequired)
{
@this.Invoke(new SetPropertyThreadSafeDelegate<TPropertyType>(SetPropertyThreadSafe), new object[] { @this, property, value });
}
else
{
@this.GetType().InvokeMember(propertyInfo.Name, BindingFlags.SetProperty, null, @this, new object[] { value });
}
}
It's called like so:
downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, 32);
The reason for doing this is to get compile-time checking of property names and type assignments. It works perfectly for standard objects, but everything goes a bit pear-shaped with value types because the compiler is happy to accept the following, which of course bombs at runtime:
downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, 'c');
downloadProgressBar.SetPropertyThreadSafe(() => downloadProgressBar.Step, long.MaxValue);
I've already modified the SetPropertyThreadSafe
method to handle the case when value types are used, and throw an exception if the incorrect type is used as an argument, but what I'm really loooking for is the ability to get this method to perform compile-time type checking for 100% of cases, i.e. objects and value types. Is this even possible and if so how would I need to modify my code to do this?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
将契约更改为:
请注意,这样您就不再需要执行 IsAssignableFrom 检查,因为编译器将强制执行它。
您的示例编译的原因是编译器猜测了类型参数是什么。以下是编译器将这些调用转换为的内容:
请注意第一个调用是 int,这是因为 ProgressBar.Step 是 int,而“c”是 char,它隐式转换为 int。与下一个示例相同,int 隐式转换为 long,而第二个是 long,因此编译器猜测它是 long。
如果您希望像这样的继承和转换起作用,请不要让编译器猜测。您的两个解决方案是:
当然,这不太理想,因为这样您基本上就对 Func 的类型进行了硬编码。您真正想做的是让编译器确定这两种类型并告诉您它们是否兼容。
注意:下面是我将使用的代码,它与您的完全不同:
Change the contract to:
Note that with this you no longer need to do the IsAssignableFrom check since the compiler will enforce it.
The reasons your example compiled is because the compiler made a guess as to what the type parameter was. Here is what the compiler turns those calls into:
Notice how the first one is int, that's because ProgressBar.Step is an int and 'c' is a char which has an implicit conversion to int. Same with the next example, int has an implicit conversion to long, and the second one is long, so the compiler guesses that it is long.
If you want inheritance and conversions like those to work, don't make the compiler guess. Your two solutions are:
Of course this is less than ideal, because then you are basically hard coding in the type of the Func. What you really want to do is let the compiler determine both types and tell you if they are compatible.
NOTE: Below is the code that I would use, which is entirely different from yours:
你不能。 Linq 使用在运行时评估的表达式树。
我建议为您的查询创建单元测试。
You can't. Linq uses expression trees which are evaluated at runtime.
I would suggest creating unit tests for your queries.
您只需要对泛型和表达式进行一些小的更改:
然后您提供一个 lambda,如下所示:
您指定的值必须与 SomeProperty 的类型协变,并且会在编译时检查这一点。如果我误解了什么,请告诉我。如果您需要将其限制为 Control,只需将签名更改为
或
You just need to make some minor changes to your generic and expression:
Then you supply a lambda like so:
The value you specify must be covariant to the type of SomeProperty, and this is checked at compile time. Let me know if I'm misunderstanding something. If you need to constrain it to Control, you simply change the signature to
or