如何在 C# 中定义高阶函数的参数名称

发布于 2024-09-30 01:18:03 字数 1246 浏览 8 评论 0原文

在 C# 中,可以创建高阶函数,即函数 g 将函数作为参数。假设我想创建这样一个函数,它给定一个函数 f 并返回另一个扩展其功能的函数。 如何为返回的增强方法定义参数名称?动机是,我通常使用更高阶的方法,其中一些会产生新方法..并且这些可能很难使用因为它们没有附加参数名称等。

说明如何在 C# 中分别定义 gf 的示例:

我定义了一个 Extend 方法,它可以扩展采用 T 作为参数的方法,并且返回一个S。 <代码>

static class M
{
    public static Func< T, S> Extend(Func< T, S> functionToWrap)
    {
      return (someT) =>
      {
        ...
        var result = functionToWrap(someT);
        ...
        return result;
      };
    }
}

然后我们可以在类上扩展方法而不更改该方法。 <代码>

class Calc2
{
    public Func< int, int> Calc;
    public Calc2()
    {
      Calc = M.Extend< int, int>(CalcPriv);
    }
    private int CalcPriv(int positiveNumber)
    {
      if(positiveNumber < 0) throw new Exception(...);
      Console.WriteLine("calc " + i);
      return i++;
    }
}

唉,参数名称 positiveNumber 不再可用,因为唯一可用的信息是 Func;计算。也就是说,当我通过输入 new Calc2().Calc(-1) 使用扩展方法时,我没有从 IDE 得到任何帮助,事实上我的参数是错误的。

如果我们可以定义一个委托并将其强制转换为该委托,那就太好了,但是这是不可能的。

有什么建议吗?

In C# it is possible to create higher order functions, ie. functions g taking functions as arguments. Say I want to create such a function which given a function f and returns another function extending its functionality. How do I define argument names for the returned enhanced method? The motivation being, that I'm working with higher order methods in general, some of which produce new methods.. and these can be difficult to use as there is no parameter names etc. attached to them.

An example illustrating how g and f respectively could be defined in C#:

I define a method Extend that can extens methods taking a T as argument and returning an S.

static class M
{
    public static Func< T, S> Extend(Func< T, S> functionToWrap)
    {
      return (someT) =>
      {
        ...
        var result = functionToWrap(someT);
        ...
        return result;
      };
    }
}

We can then extend a method on our class without changing the method.

class Calc2
{
    public Func< int, int> Calc;
    public Calc2()
    {
      Calc = M.Extend< int, int>(CalcPriv);
    }
    private int CalcPriv(int positiveNumber)
    {
      if(positiveNumber < 0) throw new Exception(...);
      Console.WriteLine("calc " + i);
      return i++;
    }
}

Alas, the argument name positiveNumber is no longer available, since the only available information is Func<int, int> Calc. That is when I use the extended method by typing new Calc2().Calc(-1) I get no help from the IDE that in fact my argument is wrong.

It would be nice if we could define a delegate and cast it to this, however, this is not possible.

Any suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

恬淡成诗 2024-10-07 01:18:03

如果您只想要带有命名参数的固定委托类型,那么您可以定义自己的委托类型:

Func 的定义如下:

public delegate TResult Func<in T, out TResult>(T arg)

因此您可以使用所需的参数名称定义自己的委托类型。

但在您的示例中,您希望保留传入的委托类型,因此这在这里不起作用。理论上,您可以这样定义您的函数:

public static T Extend(T functionToWrap)
{
}

不幸的是,没有良好的通用约束将输入类型限制为具有正确签名的委托(或者甚至只是委托)。但如果没有这些限制,实现将变得如此丑陋,并且您将失去如此多的静态类型安全性,在我看来这是不值得的。

一种解决方法是使用:

new MyFunc(Extend(f))

其中 MyFunc 定义您想要的参数名称。

或者您可以执行以下操作:

public static T ConvertDelegate<T>(Delegate d)
{
    if (!(typeof(T).IsSubclassOf(typeof(Delegate))))
        throw new ArgumentException("T is no Delegate");
    if (d == null)
        throw new ArgumentNullException();
    MulticastDelegate md = d as MulticastDelegate;
    Delegate[] invList = null;
    int invCount = 1;
    if (md != null)
        invList = md.GetInvocationList();
    if (invList != null)
        invCount = invList.Length;
    if (invCount == 1)
    {
        return (T)(object)Delegate.CreateDelegate(typeof(T), d.Target, d.Method);
    }
    else
    {
        for (int i = 0; i < invList.Length; i++)
        {
            invList[i] = (Delegate)(object)ConvertDelegate<T>(invList[i]);
            }
            return (T)(object)MulticastDelegate.Combine(invList);
        }
    }

public static TDelegate Extend<TDelegate,TArg,TResult>(Func<TArg,TResult> functionToWrap)
where TDelegate:class
    {       
        Func<TArg,TResult> wrappedFunc= DoTheWrapping(functionToWrap);
        return ConvertDelegate<TDelegate>(wrappedFunc);
    }

顺便说一句,即使在 .net 4 之前,也可以使用 ConvertDelegate 函数来获取委托上的 Co/Contravariance。

If you only want a fixed delegate type with named parameters then you can just define your own delegate type:

Func is just defined like this:

public delegate TResult Func<in T, out TResult>(T arg)

So you can define your own delegate type with the parametername you want.

But in your example you want to preserve the delegate type passed in, so this doesn't work here. In theory you could define your function like this:

public static T Extend(T functionToWrap)
{
}

Unfortunately there are no good generic constraints which restrict the input type to a delegate with the right signature(Or even just delegates at all). But without these constraints the implementation would become so ugly, and you'd lose so much static type safety that IMO it's not worth it.

One workaround is using:

new MyFunc(Extend(f))

where MyFunc defines the parameternames you want.

Or you could do the following:

public static T ConvertDelegate<T>(Delegate d)
{
    if (!(typeof(T).IsSubclassOf(typeof(Delegate))))
        throw new ArgumentException("T is no Delegate");
    if (d == null)
        throw new ArgumentNullException();
    MulticastDelegate md = d as MulticastDelegate;
    Delegate[] invList = null;
    int invCount = 1;
    if (md != null)
        invList = md.GetInvocationList();
    if (invList != null)
        invCount = invList.Length;
    if (invCount == 1)
    {
        return (T)(object)Delegate.CreateDelegate(typeof(T), d.Target, d.Method);
    }
    else
    {
        for (int i = 0; i < invList.Length; i++)
        {
            invList[i] = (Delegate)(object)ConvertDelegate<T>(invList[i]);
            }
            return (T)(object)MulticastDelegate.Combine(invList);
        }
    }

public static TDelegate Extend<TDelegate,TArg,TResult>(Func<TArg,TResult> functionToWrap)
where TDelegate:class
    {       
        Func<TArg,TResult> wrappedFunc= DoTheWrapping(functionToWrap);
        return ConvertDelegate<TDelegate>(wrappedFunc);
    }

BTW the ConvertDelegate function can be used to get Co/Contravariance on Delegates even prior to .net 4.

空‖城人不在 2024-10-07 01:18:03

通过将新构造的委托动态绑定到底层 Func 委托方法,可以转换为具有命名参数的委托:

public delegate double CalcFunc(double value);

static class M
{
    public static Func<T, S> Extend<T,S>(Func<T, S> functionToWrap)
    {
      return (someT) => functionToWrap(someT);
    }
}

class Program
{
    private static double Calc(double input)
    {
        return 2*input;
    }

    [STAThread]
    static void Main()
    {
        Func<double, double> extended = M.Extend<double, double>(Calc);

        CalcFunc casted = (CalcFunc)Delegate.CreateDelegate(typeof(CalcFunc), extended.Target, extended.Method);
        Console.WriteLine(casted(2) + " == 4");
        Console.WriteLine("I didn't crash!");
        Console.ReadKey();
    }
}

警告:这不会对转换进行任何编译时检查。如果签名不完全匹配,您将在运行时遇到绑定失败(.NET 4 中对逆变的特殊支持除外)。

It is possible to cast to a delegate with named parameters, by dynamically binding a newly constructed delegate to the underlying Func delegate method:

public delegate double CalcFunc(double value);

static class M
{
    public static Func<T, S> Extend<T,S>(Func<T, S> functionToWrap)
    {
      return (someT) => functionToWrap(someT);
    }
}

class Program
{
    private static double Calc(double input)
    {
        return 2*input;
    }

    [STAThread]
    static void Main()
    {
        Func<double, double> extended = M.Extend<double, double>(Calc);

        CalcFunc casted = (CalcFunc)Delegate.CreateDelegate(typeof(CalcFunc), extended.Target, extended.Method);
        Console.WriteLine(casted(2) + " == 4");
        Console.WriteLine("I didn't crash!");
        Console.ReadKey();
    }
}

One word of warning: this will not do any compile-time checking of the cast. If the signatures don't match exactly, you'll get a bind failure at runtime (excepting special support for contravariance in .NET 4).

亣腦蒛氧 2024-10-07 01:18:03

这就是匿名代表的美妙之处;他们是匿名的。 Func 是接受 int 并返回 int 的方法的委托。该函数实际执行的操作以及参数的名称都是无关紧要的。

唯一可行的方法是 Calc 属于命名委托类型,并使用与 CalcPriv 相同的签名进行定义。它仍然会像编写的那样工作,包括匿名扩展,但你会有一个 Calc 的命名参数。

传递信息的另一种方法是使用 ///

标签传递到 xml-doc Calc,该标签描述了 Calc 设计采用的参数。

最后,您可以从 Func 派生来创建 TakesPositiveInteger 类。这有点远了,但如果你想谈论自记录代码......

That's the beauty of anonymous delegates; they're anonymous. A Func is a delegate to a method that takes an int and returns an int. What the function actually does, and therefore the names of the parameters, is irrelevant.

The only way this would work is if Calc were of a named delegate type, defined with a signature identical to CalcPriv. It would all still work as written, including the anonymous extension, but you'd have a named parameter for Calc.

Another way to impart the information would be to xml-doc Calc with a ///<summary></summary> tag describing the parameter Calc was designed to take.

Lastly, you could derive from Func<T,TResult> to create a TakesPositiveInteger<T,TResult> class. This is going a little far, but if you wanna talk about self-documenting code...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文