在我开始解释代码之前,我将首先提供我的用例,以便您了解正在发生的事情和原因。
先决条件:
工作流:
到目前为止,一切都很好,而且完美无瑕。
现在技术部分:
CallQueue是一个使用lambda表达式作为泛型方法的输入的类,并存储在客户端执行队列中调用所需的反射数据。
为什么所有这些复杂性...... lambda泛型等?类型安全。 管理客户端是愚蠢的,只需要知道几个脚本方法来编写,而不是真正的程序员。因此,通常可以发送整数而不是字符串或使用拼写错误命名属性。这就是脚本使用lambdas和泛型来限制某人可以输入的内容的原因。
这会在服务器上编译,如果错误则会被拒绝。
这是管理客户端写的符号脚本:
CallQueue cc = new CallQueue(new DateTime(2012,12,21,10,0,0));
// set property Firstname to "test person"
cc.AddPropertySet<Person, string>(x => x.FirstName, "test person");
// call method ChangeDescription with parameter "test order"
cc.AddVoidMethodCall<Order, string>(x => x.ChangeDescription, "test order");
// call method Utility.CreateGuid and send result to Person.PersonId
cc.AddFunctionCallWithDestinationPropertySet<Utility, Guid, Person>(src => src.CreateGuid, dst => dst.PersonId);
客户端将获得的是CallQueue实例,并执行它:
Order order = new Order();
Person person = new Person();
Utility util = new Utility();
CallQueue cc = /* already got from server */;
// when you call this execute the call queue will do the work
// on object instances sent inside the execute method
cc.Execute(new List<object> { order, person, util });
到目前为止,一切都很好并且类型安全,但有一些含义:
以例如:
cc.AddFunctionCall<Int32, string>(x => x.ToString);
这将编译但在客户端执行时会失败,因为它不会将Int32发送到execute方法。
好的,bla bla bla .... 所以问题是:
如何使用一组允许的类型限制这些泛型方法 - 而不是通过定义继承:
where T : something
但更像是
where listOftypes.Contains(T)
或任何限制可以进入的内容的等效解决方案...... 我没有找到这个通用的约束......
这是CallQueue类:
[Serializable]
public class CallQueue : List<CallQueue.Call>
{
[Serializable]
public struct Call {
public MethodInfo Method;
public MethodInfo DestinationProperty;
public object[] Parameters;
public Call(MethodInfo m, MethodInfo d, object[] p) {
Method = m;
Parameters = p;
DestinationProperty = d;
}
}
public CallQueue(DateTime when) {
ScheduledTime = when;
}
public DateTime ScheduledTime
{
get;
set;
}
public void AddFunctionCall<TSrcClass, TResult>(Expression<Func<TSrcClass, Func<TResult>>> expr)
{
MethodResolver((LambdaExpression)expr, null, new object[] {});
}
public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TResult, TDest>(Expression<Func<TSrcClass, Func<TResult>>> expr, Expression<Func<TDest, TResult>> dest)
{
MethodResolver((LambdaExpression)expr, dest, new object[] { });
}
public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param, Expression<Func<TDest, TResult>> dest)
{
MethodResolver((LambdaExpression)expr, dest, new object[] { param });
}
public void AddFunctionCall<TSrcClass, TParam1, TResult>(Expression<Func<TSrcClass, Func<TParam1, TResult>>> expr, TParam1 param)
{
MethodResolver((LambdaExpression)expr, null, new object[] {param});
}
public void AddFunctionCallWithDestinationPropertySet<TSrcClass, TParam1, TParam2, TResult, TDest>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2, Expression<Func<TDest, TResult>> dest)
{
MethodResolver((LambdaExpression)expr, dest, new object[] { param, param2 });
}
public void AddFunctionCall<TSrcClass, TParam1, TParam2, TResult>(Expression<Func<TSrcClass, Func<TParam1, TParam2, TResult>>> expr, TParam1 param, TParam2 param2)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
}
public void AddVoidMethodCall<TSrcClass, TParam>(Expression<Func<TSrcClass, Action<TParam>>> expr, TParam param)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param });
}
public void AddVoidMethodCall<TSrcClass, TParam1, TParam2>(Expression<Func<TSrcClass, Action<TParam1, TParam2>>> expr, TParam1 param, TParam2 param2)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param, param2 });
}
public void AddVoidMethodCall<TSrcClass, TParam1, TParam2, TParam3>(Expression<Func<TSrcClass, Action<TParam1, TParam2, TParam3>>> expr, TParam1 param, TParam2 param2, TParam3 param3)
{
MethodResolver((LambdaExpression)expr, null, new object[] { param, param2, param3 });
}
public void AddPropertySet<TSrcClass, TParam1>(Expression<Func<TSrcClass, TParam1>> expr, TParam1 param)
{
PropertyResolver((LambdaExpression)expr, new object[] {param});
}
public void Execute(List<object> instances) {
foreach (var call in this) {
var owner = instances.Find(o => o.GetType() == call.Method.DeclaringType);
if (call.DestinationProperty != null)
{
// execute method get result and set to destination property
object res = call.Method.Invoke(owner, call.Parameters);
var destOwner = instances.Find(o => o.GetType() == call.DestinationProperty.DeclaringType);
call.DestinationProperty.Invoke(destOwner, new object[] {res});
}
else
{
// just execute method
call.Method.Invoke(owner, call.Parameters);
}
}
}
private void MethodResolver(LambdaExpression expr, LambdaExpression dest, object[] param)
{
var body = (UnaryExpression)expr.Body;
var methodCall = (MethodCallExpression)body.Operand;
var constant = (ConstantExpression)methodCall.Arguments[2];
var method = (MethodInfo)constant.Value;
MethodInfo dmethod = null;
if (dest != null)
{
var prop = (MemberExpression)dest.Body;
var propMember = (PropertyInfo)prop.Member;
dmethod = propMember.GetSetMethod();
}
this.Add(new Call(method, dmethod, param));
Console.WriteLine(method.Name);
}
private void PropertyResolver(LambdaExpression expr, object[] param)
{
var prop = (MemberExpression)expr.Body;
var propMember = (PropertyInfo)prop.Member;
var method = propMember.GetSetMethod();
this.Add(new Call(method, null, param));
Console.WriteLine(method.Name);
}
}
非常感谢你。 干杯!
答案 0 :(得分:2)
所以你可以做两件事。一个是确保listOftypes
中的所有类型都来自相同的基类,或实现相同的接口,在这种情况下,您可以只使用where。
鉴于您的问题似乎表明这不是您想要的,您可以通过查看typeof(T)
并查看listOfTypes
中是否包含该类型,在运行时获得更好的错误报告。不如你想要的那么好,但你是有限的。
答案 1 :(得分:1)
你不能做你所要求的,例如:
where listOftypes.Contains(T)
但 可以应用多种类型,但您确实需要输入它们而不是按照您的建议维护集合。
There are predefined ways to apply constraints:
其中T:struct
type参数必须是值 类型。除Nullable外的任何值类型 可以指定。请参阅使用Nullable 类型(C#编程指南)了解更多 信息。
其中T:class
type参数必须是引用 类型;这也适用于任何类, 接口,委托或数组类型。
其中T:new()
type参数必须是public 无参数构造函数。使用时 连同其他约束, 必须指定new()约束 最后。
其中T:
必须是或派生类型参数 来自指定的基类。
其中T:
type参数必须是或实现的 指定的接口。多 接口约束可以 指定。约束界面 也可以是通用的。
其中T:U
为T提供的类型参数必须 是或来自论证 为美国提供。