如何使用可变数量的参数传递Func <t>

时间:2018-05-29 22:06:41

标签: c# func

所以我试图能够传递一个带有可变数量参数的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,但它们似乎都需要一些我不想使用的调用语法。

有关如何实现这一目标的任何想法?

5 个答案:

答案 0 :(得分:3)

答案是 don 。首先,您尝试调用不带参数的方法并返回一些对象。你不能只是&#34; make&#34;一个需要此类型参数的函数,否则您将如何使用所需参数调用它。

您已经创建了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>的委托签名,以便您可以对单元测试进行填充和存根。此外,如果任何参数的计算成本很高,您可以将它们公开为惰性属性,并且只有在调用时才会获取它们。