C# 函数编写

本文关键字:函数 | 更新日期: 2023-09-27 17:56:29

关于用C#编写代码的简单问题。以这种方式编写函数有什么价值吗

Func<int, int> add2 = x => x + 2;

int add2(int x)
{
    return x + 2;
}

发现第二个示例更容易阅读,或者我是否错过了为什么第一个函数以这种方式编写的非常具体的原因?

C# 函数编写

第一个是函数对象,而第二个只是一个函数/方法。不同之处在于,第一个是 .Net 平台的 Object hirachy 中的一个对象,而后者只是一个不在该 hirachy 中的函数。但据我所知,C# 会在您需要自动装箱时为您进行第二种情况的自动装箱。另一个区别可能是这些函数在反射 API 中的外观。虽然第一个只是一个具有函数值的变量,但第二个实际上是 API 中的一个函数。

第一个可以被认为是等效于函数指针,它可以是空的,只是等待接收"委托"。在您介绍的情况下,它的实现已经使用 lambda 表达式声明。第二个是一个实际的常规函数,也可以作为参数传递给具有等效模板类型的Func

int add2(int x)
{
    return x + 2;
}
int main()
{   
    Func<int, int> add2Func = add2;
    // Invoking:
    int retVal = add2Func.Invoke(10); // will call the add2 function
}

这两个代码(假设它们都在方法之外)含义不同。第一个声明一个 Func<int, int> 类型的字段(一个委托接受int并返回 int ),并为其分配一个 lambda 表达式。第二个只声明了一个带有主体的正常方法。区别在于(除了第一个是字段,第二个是方法)您可以更改 add2 的值,如果它是一个字段。

Lambda 表达式很有用,因为它们编写时间很短,使用类型推断来确定参数的类型 (x ),如果未指定为块,则隐式返回值。这就是为什么它们主要用于 LINQ 的原因,因为它们可以很好地放在一行上。还有第二种语法,匿名方法,如下所示:

Func<int, int> add2 = delegate(int x)
{
    return x + 2;
};

匿名方法的优点是可以省略参数(delegate{ ... }),并且它匹配任何委托类型。

是的,正如其他人所说,严格来说,您谈论的是两件不同的事情。 但是您的问题仍然很重要,因为在不同的情况下,您经常面临必须在使用 lambda 编写一些代码或编写形式化方法之间进行选择。

选择主要是风格问题。我会说,有时,有些人过度使用 lambda 风格只是因为他们试图变得聪明,并希望看看他们可以在尽可能小的空间内放置多少代码。 但它肯定会变得更难阅读。

然而,肯定在某些情况下,使用 lambda 会提高可读性和便利性。

一个很好的例子是将其与可链接的 LINQ 方法一起使用时。

您觉得哪个版本更具可读性和便利性?

拉姆达版本

List<int> list = new List<int> { 6, 3, 9, 2, 6, 9, 7, 11, -4 };
IEnumerable<int> processedList = list.Where(i => i % 2 == 0).OrderBy(i => i);

普通方法版本

List<int> list = new List<int> { 6, 3, 9, 2, 6, 9, 7, 11, -4 };
IEnumerable<int> processedList = list.Where(FilterByNumber).OrderBy(OrderByNumber);
private bool FilterByNumber(int i)
{
    return i % 2 == 0;
}
private int OrderByNumber(int i)
{
    return i;
}

只需做您觉得更舒服的事情,当您以后不得不更改代码时,这不会让您在事后挠头。

创建

等效表达式的方法:

Func<int, int> add2 = x => x + 2;

是要做的:

Expression<Func<int, int>> add2 = x => x + 2;

事实上,我们甚至不需要知道代码中发生了什么:

something.Select(x => x + 2);

编译器创建一个委托(根据第一个)或表达式(根据第二个),具体取决于哪个更有用。(最有可能somethingIQueryable<int>,在这种情况下将生成表达式,或者其他类型的IEnumerable<int>在这种情况下将生成Func)。

创建等效表达式的方法如下:

int add2(int x)
{
  return x + 2;
}

(也就是说,避免 lambda 语法),是:

var parExp = Expression.Parameter(typeof(int), "x");
var lamda = Expression.Lambda<Func<int, int>>(
    Expression.Add(parExp, Expression.Constant(2, typeof(int))), parExp);

相比之下,我会说x => x + 2更具可读性。

在不太极端的情况下,lambda 语法可以立即更具可读性,一种特殊情况是连续使用多个语法,就像 Linq 经常发生的那样,当然在许多其他情况下也是如此。

它还增加了整个应用程序的可读性,像add2这样的小简单方法仅在使用*时定义,而不是用大量此类方法填充类。考虑一下这个略微修改的实际代码:

var lastModAndTag = await ctx.Pending
  .Select(pn => new {pn.Modified, pn.Tag})
  .Union(ctx.Preferences.Select(pr => new {pr.Modified, pr.Tag}))
  .OrderByDescending(dt => dt.Modified).FirstOrDefaultAsync();

具有以下功能:

private class DateAndTag // Can't use anonymous types as we have to pass method boundaries.
{
  public DateTime Modified { get; set; }
  public string Modified { get; set; }
}
private static DateAndTagFromPending(Pending pn)
{
  return new DateAndTag{Modified = pn.Modified, Tag = pn.Tag};
}
private static DateAndTagFromPreference(Preference pr)
{
  return new DateAndTag{Modified = pr.Modified, Tag = pr.Tag};
}
private static DateTime GetModified(DateAndTag dt)
{ //it's possible to get the property into a method or delegate with reflection, but that's even worse.
  return dt.Modified;
}
//and now in the calling code;
var lastModAndTag = ctx.Pending
  .Select(DateAndTagFromPending)
  .Union(ctx.Preferences.Select(DateAndTagFromPreference))
  .OrderByDescending(GetModified).FirstOrDefault();

这真的不容易阅读。而且我们不能使用Async,因为它仅适用于表达式。事实上,我们被迫从数据库中获取所有内容来运行这些函数。但即使我们不关心(因为我们在内存中工作并使用Func),它仍然需要更多的代码需要处理。

最后,如果您需要咖喱或以其他方式捕获变量,那么您的选择实际上是在:

Func<int, int> addX = val => val + x; // x is from outside of this step.

和:

Func<int, int> addX = delegate(int val)
{
  return val + x;
};

如果您不习惯 lambda 形式,现在对您来说似乎更具可读性,但对我来说肯定不是。

事实上,人们越习惯 lambda 形式,就越会使用它,因为它看起来更具可读性。

但是,如果您有一个复杂程度的方法,或者是递归的,或者需要是一个可访问的方法,那么lambda形式将没有那么有用。

*但请注意,如果您有两个与add2相同的方法,编译器通常会将它们合并到生成的程序集中的单个委托中,因此源代码中的这种重复不会浪费或担心。

有时最好

使用 Func<T2,T2,...,Tn> 而不是静态函数声明。

示例:您有一个将对象从 Type1 转换为 Type2 的泛型方法

public Tout Convert<Tin, Tout>(Tin obj, Func<Tin, Tout> convertFunc)
{
   return convertFunc(obj);
}

Class1 obj = new Class1();
Class2 convertedObject = Convert<Class1, Class2>(obj, x => new Class2());

什么是福利?Lambda 表达式更短,代码更具可读性。