如何使用现有的方法而不是lambda,当它不是静态的

本文关键字:lambda 静态 何使用 方法 | 更新日期: 2023-09-27 18:05:44

这一定是一个重复的,但我没有发现它。我发现这个问题是相关的,因为它回答了为什么建议使用方法组而不是lambda。

但是我如何使用一个现有的方法组,而不是一个lambda,如果方法不是在当前类和方法不是static ?

说我有一个整数列表,我想转换为字符串,我可以使用List.ConvertAll,但我需要传递一个Converter<int, string>给它:

List<int> ints = new List<int> { 1 };
List<string> strings = ints.ConvertAll<string>(i => i.ToString());

这可以工作,但是它使用lambda创建了一个不必要的匿名方法。因此,如果Int32.ToString将是静态的,并将采取int,我可以写:

List<string> strings = ints.ConvertAll<string>(Int32.ToString);

但是这当然不能编译。那么我该如何使用方法组呢?

如果我创建一个像这样的实例方法

string FooInt(int foo)
{
    return foo.ToString();
}

我可以使用strings = ints.ConvertAll<string>(FooInt);,但这不是我想要的。我不想创建一个新方法只是为了能够使用现有的。

如何使用现有的方法而不是lambda,当它不是静态的

框架中有一个静态方法,可用于将任何集成的数据类型转换为字符串,即Convert.ToString:

List<int> ints = new List<int> { 1 };
List<string> strings = ints.ConvertAll<string>(Convert.ToString);

由于Convert.ToString的签名也是已知的,您甚至可以消除显式的目标类型参数:

var strings = ints.ConvertAll(Convert.ToString);

这工作。然而,我也更喜欢lambda表达式,即使ReSharper告诉你一些不同的东西。ReSharper有时候优化得太多了。它阻止开发人员考虑他们的代码,特别是在可读性方面。

更新

根据Tim的评论,我将尝试解释在这种特殊情况下lambda和静态方法组调用之间的区别。因此,我首先研究了mscorlib反汇编,以弄清楚整型到字符串的转换是如何工作的。Int32.ToString方法调用System命名空间的Number类中的外部方法:

[__DynamicallyInvokable, TargetedPatchingOptOut("Performance critical to inline across NGen image boundaries"), SecuritySafeCritical]
public string ToString(IFormatProvider provider)
{
    return Number.FormatInt32(this, null, NumberFormatInfo.GetInstance(provider));
}

静态Convert.ToString成员除了在形参上调用ToString之外什么也不做:

[__DynamicallyInvokable]
public static string ToString(int value)
{
    return value.ToString(CultureInfo.CurrentCulture);
}
从技术上讲,如果您编写自己的静态成员或扩展,就像您在问题中所做的那样,则没有区别。这两条直线的区别是什么?

ints.ConvertAll<string>(i => i.ToString());
ints.ConvertAll(Convert.ToString);

从技术上讲,两者也没有区别。第一个例子创建了一个匿名方法,它返回一个字符串并接受一个整数。使用整数的实例,它调用它的成员ToString。第二个方法也是如此,不同之处在于该方法不是匿名的,而是框架的集成成员。

唯一的区别是第二行更短,为编译器节省了一些操作。

但是为什么不能直接调用非静态的ToString呢?

让我们看一下List:

ConvertAll -方法
public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter)
{
    if (converter == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.converter);
    }
    List<TOutput> list = new List<TOutput>(this._size);
    for (int i = 0; i < this._size; i++)
    {
        list._items[i] = converter(this._items[i]);
    }
    list._size = this._size;
    return list;
}

列表遍历每个项,使用该项作为参数调用转换器,并将结果复制到最后返回的新列表中。

所以这里唯一的关系是你的converter被显式调用。如果可以将Int32.ToString传递给方法,编译器将不得不决定在循环中调用this._items[i].ToString()。在这种特殊情况下,它可以工作,但这对编译器来说"太聪明了"。类型系统不支持这种代码转换。相反,转换器是一个对象,描述可以从被调用方的作用域调用的方法。这要么是一个现有的静态方法,如Convert.ToString,要么是一个匿名表达式,如lambda。

是什么导致基准测试结果的差异?

这很难猜。我能想到两个因素:

  1. 计算lambda可能导致运行时开销。
  2. 可以优化框架调用。

最后一点特别意味着JITer能够内联调用,从而获得更好的性能。然而,这些只是我的假设。如果有人能澄清这一点,我将不胜感激!:)

你一针见血:

方法创建了一个不必要的匿名方法λ。

你不能做你想要的,因为没有合适的方法组可以使用,所以匿名方法是必要的。它在另一种情况下有效,因为隐式范围变量传递给方法组创建的委托。在您的示例中,需要在 range变量上调用方法。这是一个完全不同的场景