我正在尝试将编译时间检查添加到以前采用数据对象属性的字符串名称和'object'类型的值的代码中。我这样做是为了确保属性和值实际上是同一类型,以防止在没有引入运行时错误的情况下使用
。我正在通过创建一个方法来处理编译时检查,该方法采用类型为Expression<Func<TDataObject, TPropertyValue>>
的表达式和类型为TPropertyValue
的参数。由此,我可以检查表达式以获取将要返回的属性的名称,然后使用与字符串和值作为对象类型的当今相同的逻辑。
public interface IPropertyDictionary<TDataObject> where TDataObject : class
{
void AddIfSameType<TProperty>(
Expression<Func<TDataObject, TProperty>> propertyAccessExpression,
TProperty propertyValue);
}
以下按预期工作:
// Allowed
propDictionary.AddIfSameType(e => e.IntProperty, 123);
// Flagged by intellisense (though the expression is flagged rather than second parameter...)
propDictionary.AddIfSameType(e => e.IntProperty, "asdf");
但是,这无法按预期进行:
// Not flagged as error
propDictionary.AddIfSameType(e => e.IntProperty, 123L);
这样,C#就会将TPropertyValue推断为 long 而不是int。在调试器中,我可以看到表达式已被转换为强制转换为长整数:
e => Convert(e.IntProperty)
在我的理想情况下,C#在进行类型推断时会更喜欢IntProperty的类型,并引发编译时错误,指示从long到int的转换需要显式转换。有没有办法向C#指示在推断类型时,它仅应使用方法的第一个参数?我目前唯一的选择是显式提供type参数:
// Flagged by intellisense
propDictionary.AddIfSameType<int>(e => e.IntProperty, 123L);
但是在99%的情况下,人们不会传递类型参数,并且我不希望他们意识到在这种情况下需要这样做。结果,它们的错误再次成为我渴望避免的运行时错误之一。
答案 0 :(得分:6)
是的,以稍微绕行的方式。使用两个类型参数和一个type parameter constraint。没有平等约束,但是继承约束在大多数情况下都适用:
static void AddIfSameType<TLProp,TRProp>(Func<DataObject,TLProp> lprop, TRProp rprop) where TRProp : TLProp
{
}
static void Main(string[] args)
{
AddIfSameType(d => d.IntProperty, 1);
//compiles
AddIfSameType(d => d.IntProperty, 1L);
//Error CS0315 The type 'long' cannot be used as type parameter 'TRProp'
//... There is no boxing conversion from 'long' to 'int'.
}
答案 1 :(得分:4)
在保留所需的确切语法的同时,没有一种方法可以完成所描述的内容。(我的立场是正确的:有关方法,请参阅David Browne的回答。)< / p>
如果您愿意更改方法,则可以将带有表达式的方法调用与带有值的方法调用分开。像这样:
propDictionary.AdderFor(e => e.IntProperty).Add(123L);
此方法的一个潜在好处是您可以将“加法器”捕获为变量。
var intAdder = propDictionary.AdderFor(e => e.IntProperty)
intAdder.Add(456); // okay
intAdder.Add(123L); // error
答案 2 :(得分:0)
C#应该如何知道TDataObject
具有属性IntProperty
?您只说TDataObject
必须是class
的{{1}}。您必须指定一个约束,使C#知道此属性(可能还有其他属性)。例如
where TDataObject : class
然后您可以使用声明字典
public interface IProperties
{
int IntProperty { get; set; }
double DoubleProperty { get; set; }
string StringProperty { get; set; }
}
public interface IPropertyDictionary<TDataObject> where TDataObject : IProperties
{
void AddIfSameType<TProperty>(
Expression<Func<TDataObject, TProperty>> getProp, TProperty value);
}
和数据类
public class PropDictionary<TDataObject> : IPropertyDictionary<TDataObject>
where TDataObject : IProperties
{
public void AddIfSameType<TProperty>(
Expression<Func<TDataObject, TProperty>> getProp, TProperty value)
{
}
}
现在这两个电话都可以工作
public class DataObject : IProperties
{
public int IntProperty { get; set; }
public double DoubleProperty { get; set; }
public string StringProperty { get; set; }
}
它们为什么起作用?
第一个是因为类型被推断为
var propDictionary = new PropDictionary<DataObject>();
propDictionary.AddIfSameType(e => e.DoubleProperty, 123);
propDictionary.AddIfSameType(e => e.IntProperty, 123L);
传递的void ProperDictionary<DataObject>.AddIfSameType<double>(
Expression<Func<DataObject, double>> getProp, double value)
值只是强制转换为int
。
第二种情况有点令人惊讶。类型不正确
double
我假定返回值被隐式加宽:
void ProperDictionary<DataObject>.AddIfSameType<long>(
Expression<Func<DataObject, long>> getProp, longvalue)
结论:C#比您想象的要聪明。它会推断出通用类型并自动转换值以使其在可能的情况下起作用。
更新
因此,对e => (long)e.IntProperty
返回类型和值使用两个不同的类型参数。
Func
C#将为每个这些类型参数推断出确切的类型。您也可以改将值键入为public void AddIfSameType<TProp, TValue>(
Expression<Func<TDataObject, TProp>> getProp, TValue value)
{
if (typeof(TProp) == typeof(TValue)) {
} else {
}
}
,但需要装箱值类型。