所以我试图能够传递一个带有可变数量参数的Func。
类似的东西:
public object GetValue<T>(string name, Func<object> func) {
var result = func.DynamicInvoke();
}
当知道func的参数个数时,上面的函数/签名很有用。但是当你想要在运行时之前知道参数的数量时,它会很快崩溃。
我想更改方法签名以允许以下方案,而不使用方法重载:
// No arguments
var result = GetValue("Bob", () => { return "Bob Smith"; });
// 1 argument
var result = GetValue("Joe", (i) => { return "Joe " + i.ToString(); });
// 2 arguments
var result = GetValue("Henry", (i,e) => {
return $"i: {i.ToString()}, e: {e.ToString()}";
});
现在不需要超过2个参数..但可能在将来。调用语法对我来说是最重要的一点。我宁愿不让来电者投出任何东西。
我已经看了this question and the answers,但它们似乎都需要一些我不想使用的调用语法。
有关如何实现这一目标的任何想法?
答案 0 :(得分:3)
答案是 don
您已经创建了lambdas,您需要关闭&#34;参数&#34;您希望通过这种方式有效地添加其他参数。
// No arguments
var result = GetValue("Bob", () => { return "Bob Smith"; });
// 1 argument
var i = ...;
var result = GetValue("Joe", () => { return "Joe " + i.ToString(); });
// 2 arguments
var i = ...;
var e = ...;
var result = GetValue("Henry", () => {
return $"i: {i.ToString()}, e: {e.ToString()}";
});
否则,如果您确实希望传入包含任意数量参数的任何委托,请创建参数Delegate
,但必须提供委托的确切类型,并且您必须提供呼叫的参数。
public object GetValue<T>(string name, Delegate func, params object[] args) {
var result = func.DynamicInvoke(args);
}
var result = GetValue("Bob", new Func<object>(() => { return "Bob Smith"; }));
// 1 argument
var result = GetValue("Joe", new Func<T, object>((i) => { return "Joe " + i.ToString(); }), argI);
// 2 arguments
var result = GetValue("Henry", new Func<T1, T2, object>((i,e) => {
return $"i: {i.ToString()}, e: {e.ToString()}";
}), argI, argE);
答案 1 :(得分:2)
首先,你正在打败静态类型的目的:在静态类型中,重点是在编译时你可以确切地知道某个对象具有什么类型,因此编译器会允许你做某些事情。你在这里期待的是动态类型通常提供的东西,你可以只传递“东西”,然后你可以在运行时弄清楚如何使用它。
你应该重新考虑你的这个要求,看看你是否能够以更好的,静态类型的方式解决这个问题。
话虽这么说,使用自定义类型和一些隐式类型转换(在编译时发生!)有一种有点丑陋的方式使这种静态理智:
public object GetValue(string name, DynamicFunc func)
{
return func.DynamicInvoke("a", "b");
}
public class DynamicFunc
{
public Func<object> None { get; private set; }
public Func<object, object> One {get; private set;}
public Func<object, object, object> Two { get; private set; }
public object DynamicInvoke(object param1 = null, object param2 = null)
{
if (Two != null)
return Two(param1, param2);
else if (One != null)
return One(param1 ?? param2);
else if (None != null)
return None();
return null;
}
public static implicit operator DynamicFunc(Func<object> func)
=> new DynamicFunc { None = func };
public static implicit operator DynamicFunc(Func<object, object> func)
=> new DynamicFunc { One = func };
public static implicit operator DynamicFunc(Func<object, object, object> func)
=> new DynamicFunc { Two = func };
}
然后你可以像这样使用它:
var result0 = GetValue("Bob", (Func<object>)(() => { return "Bob Smith"; }));
var result1 = GetValue("Joe", (Func<object, object>)((i) => { return "Joe " + i.ToString(); }));
var result2 = GetValue("Henry", (Func<object, object, object>)((i, e) =>
{
return $"i: {i.ToString()}, e: {e.ToString()}";
}));
请注意,您需要为lambda表达式提供一个显式类型,否则编译器将无法找出该类型。
这看起来不错还是容易理解?我不这么认为。如果你想要适当的静态类型,只需在这里使用方法重载。这样,您也不需要动态调用该函数,这也使得调用它实际上更容易。
答案 2 :(得分:1)
根据我的理解,您希望能够在调用者不需要时忽略部分参数。但是,我认为你应该首先决定如何调用委托。请注意, DynamicInvoke 的参数数量必须与实际方法的参数数量相匹配,因此如果您指定的参数多于它们接受的参数,则它将不适用于您传递的所有代理。
除非使用反射和发出来调用委托,否则必须使用重载。你制作它们的方式取决于参数是否应该是“懒惰的”。如果是这样,每个过载的主体将与其他过载的主体大不相同,以证明其存在。如果没有,它会是这样的:
object GetValue(Func<int, object> f)
{
return GetValue((i,s) => f(i));
}
object GetValue(Func<int, string, object> f)
{
return f(1, "0");
}
我不认为这是一个很好的解决方案,因为第一次重载表明只产生了第一个参数,而在引擎盖下,所有这些参数都被传递。在这种情况下,最好将所有要从方法中获取的信息包装在类中,并传递它的实例。如果您使用Lazy<T>
,这也适用于第一种情况,但代码会变得更复杂。
与您的问题相关的另一件事:有delegate{ ... }
语法,可以使用任何参数强制转换为委托类型,然后忽略这些参数。这种情况发生在呼叫网站上。
答案 3 :(得分:1)
首先,我的回答不是关于你是否应该这样做;关于这篇文章的其他答案给出了很好的解释建议/意见。
我的回答只是关于技术C#部分:如何将可变数量的参数传递给Func
。
可以通过委托来完成,可以通过params array
传递可变数量的参数,并且与Func
“兼容”。
由于params array
,Func必须是第一个参数。请注意,我在问题中移动了'name'参数,以便通过params array
传递,以便可以从Func
内访问。
下面的所有字符串都被赋值为'Bob Smith',用0或2个参数构建。
public delegate T ParamsDelegate<T>(params Object[] args);
public T GetValue<T>(ParamsDelegate<T> f, params Object[] args)
{
return f(args);
}
// 0 passed argument values.
String s0 = GetValue(args => "Bob Smith");
// 1 argument.
String s1 = GetValue(
args => String.Format("{0} Smith", args),
"Bob"
);
// 2 arguments.
String s2 = GetValue(
args => String.Format("{0} {1}", args),
"Bob", "Smith"
);
修改强>
从C#7开始,您可以通过discards打算不通过任何参数。
丢弃是您可以分配但无法读取的局部变量 从。即它们是“只写”局部变量。他们没有 相反,它们表示为_(下划线。)_是一个 contextual关键字,它与var非常相似,并且_无法读取 (即不能出现在作业的右侧。)
以下内容:
String s0 = GetValue(args => "Bob Smith");
可以改写为:
String s0 = GetValue(_ => "Bob Smith");
答案 4 :(得分:0)
如果您有传入的代表可能需要或可能不需要的变量清单,您可以将它们作为单个参数提供,可能名为CallingContext
。
class CallingContext
{
private CallingContext(string name, int index)
{
Name = name;
Index = index;
}
public string Name { get; private set; }
public int Index { get; private set; }
}
public TOut GetValue<TOut>(string name, Func<CallingContext,TOut> func) {
var i = LookupIndex(name);
var context = new CallingContext(name, i);
return func(context);
}
通过这种方式,来电者的功能可以提取所需的一切,而且你根本不必担心推送正确的东西或Func的通用参数。
var result = x.GetValue("Bob", ctxt => ctxt.Name + ctxt.Id.ToString());
将来,如果必须添加其他参数,只需将它们添加到CallingContext类即可。如果您希望该类变得非常丰富,请考虑将其作为接口公开,并使用Func<ICallingContext,TOut>
的委托签名,以便您可以对单元测试进行填充和存根。此外,如果任何参数的计算成本很高,您可以将它们公开为惰性属性,并且只有在调用时才会获取它们。