C#中的内联函数?


276

您如何在C#中执行“内联函数”?我认为我不了解这个概念。他们喜欢匿名方法吗?像lambda函数一样?

注意:答案几乎完全涉及内联函数的功能,即“手动或编译器优化,将被调用者的主体替换为函数调用站点”。如果您对匿名(又名lambda)函数感兴趣,请参阅@jalf的答案每个人都在谈论的“ Lambda”是什么?


11
最终有可能-请参阅我的回答。
konrad.kruczynski 2012年

1
有关.NET 4.5之前最接近的内容,请参阅问题如何将变量定义为lambda函数。它实际上不是作为内联编译的,而是类似于内联声明的简短函数声明。取决于您要使用内联实现的目标。
AppFzx 2013年

对于好奇的人,请查看此VS扩展程序
TripleAccretion

Answers:


384

最后,在.NET 4.5中,CLR允许使用值暗示/建议1方法内联MethodImplOptions.AggressiveInlining。它也可以在Mono的后备箱中使用(今天提交)。

// The full attribute usage is in mscorlib.dll,
// so should not need to include extra references
using System.Runtime.CompilerServices; 

...

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void MyMethod(...)

1。以前在这里使用“力”。由于有一些反对意见,我将尝试澄清这个词。如评论和文档中所述,The method should be inlined if possible.特别是考虑到Mono(已公开),存在一些特定于Mono的技术限制,其中考虑了内联或更普遍的限制(例如虚函数)。总体而言,是的,这是对编译器的提示,但我想这正是所要的。


17
+1-更新了您的答案,以更具体地说明框架版本要求。
M.Babcock 2012年

4
它仍然可能不是一个在线,但覆盖的信号不稳定启发式在大多数情况下,肯定是够的。
CodesInChaos 2012年

7
可以与所有.NET版本一起使用的另一种方法是,将稍大的方法分为两个方法,一个调用另一个方法,两个方法都不超过IL的32个字节。最终效果就像是内联原始文件。
里克·斯拉基

4
它不是在强制“ Inlining”,它只是尝试与JIT交谈并告诉程序员真正要在此处使用Inlining,但是JIT拥有最终的决定权。因此,MSDN:如果可能,应内联该方法。
Orel Eraki 2012年

11
相比之下,C ++的内联建议,甚至是特定于编译器的内联建议,实际上也没有强制内联:并非所有函数都可以内联(从根本上来说,递归函数很困难,但也有其他情况)。是的,这种“非相当”的力量是典型的。
Eamon Nerbonne

87

内联方法只是一种编译器优化,其中函数的代码将滚动到调用程序中。

在C#中没有执行此操作的机制,只能在支持它们的语言中少量使用它们-如果您不知道为什么应该在某些地方使用它们,则不应该使用它们。

编辑:澄清一下,有两个主要原因需要谨慎使用:

  1. 在不需要的情况下使用内联很容易制作大量的二进制文件
  2. 从性能的角度来看,当应该内联某些东西时,编译器往往比您更了解

最好不要管它,让编译器完成工作,然后剖析并找出内联是否是最适合您的解决方案。当然,内联某些事情(特别是数学运算符)有意义,但是让编译器处理通常是最佳实践。


35
通常我认为编译器处理内联是可以的。但是在某些情况下,我希望重写编译器的决定并内联或不内联方法。
mmmmmmmm

7
@Joel Coehoorn:这将是一个坏习惯,因为它将破坏模块化和访问保护。(考虑访问私有成员并从代码中不同点调用的类中的内联方法!)
mmmmmmmm 2009年

9
@Poma这是使用内联的奇怪原因。我高度怀疑这样做是否有效。
克里斯·喊

56
关于编译器最了解的参数是错误的。它不内联任何大于32个IL字节(周期)的方法。对于具有探查器确定的热点的项目(例如我的项目,以及上面的Egor的项目),这是可怕的,我们绝对不能做任何事情。除了剪切和粘贴代码并手动内联外,什么也没有。简而言之,当您真正提高绩效时,这是一个糟糕的状况。
明亮的

6
内联看起来更加微妙。内存具有可快速访问的缓存,并且代码的当前部分就像变量一样存储在这些缓存中。加载下一条高速缓存指令行将导致高速缓存未命中,其成本可能是单个指令的10倍或更多倍。最终,它必须加载到二级缓存中,这甚至更加昂贵。因此,code肿的代码可能会导致更多的缓存未命中,其中始终保持驻留在同一L1缓存行中的内联函数可能会导致更少的缓存未命中和可能更快的代码。使用.Net则更加复杂。

56

更新:根据konrad.kruczynski的回答,以下内容适用于4.0及以下版本的.NET。

您可以使用MethodImplAttribute类防止内联方法。

[MethodImpl(MethodImplOptions.NoInlining)]
void SomeMethod()
{
    // ...
}

...但没有办法做相反的,并迫使它内联。


2
有趣的是,为什么要阻止方法内联?我花了一些时间盯着显示器,但我无法弥补内联可能造成任何伤害的原因。
卡米洛·马丁

3
如果您收到一个调用栈(即NullReferenceException),并且显然无法将栈顶的方法扔掉。当然,它的电话之一可能有。但是哪一个呢?
dzendras 2011年

19
GetExecutingAssemblyGetCallingAssembly根据方法是否内联可以给出不同的结果。强制使用非内联方法可以消除任何不确定性。
stusmith 2012年

1
@Downvoter:如果您不同意我写的内容,则应发表评论以解释原因。不仅礼貌,而且您对20多个答案的投票也没有什么成就。
培根

2
希望我能+2,因为仅您的名字就应获得+1:D。
Retrodrone 2013年

33

您正在混淆两个单独的概念。函数内联是一种编译器优化,对语义没有影响。无论是否内联,函数的行为都相同。

另一方面,lambda函数纯粹是一个语义概念。只要遵循语言规范中规定的行为,就无需实现或执行它们。如果JIT编译器喜欢,可以内联它们,否则,可以不使用它们。

C#中没有inline关键字,因为它是通常可以留给编译器的一种优化,尤其是在JIT语言中。JIT编译器可以访问运行时统计信息,从而使其能够比编写代码时更有效地决定内联哪些内容。如果编译器决定这样做,则将内联一个函数,而您对这两种方法均无能为力。:)


20
“无论内联与否,函数的行为都相同。” 在少数情况下,情况并非如此:即记录要了解堆栈跟踪的函数。但我不想破坏你的基地声明太多:这是一般真实。
Joel Coehoorn

“而且任何一种方法都无能为力。”-不正确。即使我们没有将对编译器的“强烈建议”视为做任何事情,也可以防止内联函数。
BartoszKP

21

您是说C ++意义上的内联函数吗?正常功能的哪些内容会自动内联复制到呼叫站点中?最终结果是调用函数时实际上没有发生函数调用。

例:

inline int Add(int left, int right) { return left + right; }

如果是这样,那么否,那么没有等效的C#。

还是说在另一个函数中声明的函数?如果是,则是,C#通过匿名方法或lambda表达式支持此功能。

例:

static void Example() {
  Func<int,int,int> add = (x,y) => x + y;
  var result = add(4,6);  // 10
}

21

科迪说的没错,但是我想提供一个内联函数的例子。

假设您有以下代码:

private void OutputItem(string x)
{
    Console.WriteLine(x);

    //maybe encapsulate additional logic to decide 
    // whether to also write the message to Trace or a log file
}

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{  // let's pretend IEnumerable<T>.ToList() doesn't exist for the moment
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);
        OutputItem(y);
    }
    return result;
}

编译器刚刚在优化器可以选择修改密码,以避免重复在堆栈上放置到OutputItem()的调用,因此,这将是,如果你已经写成这样的代码:

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);

        // full OutputItem() implementation is placed here
        Console.WriteLine(y);   
    }

    return result;
}

在这种情况下,我们可以说OutputItem()函数是内联的。请注意,即使从其他位置也调用OutputItem(),也可能会这样做。

编辑以显示更可能内联的方案。


9
只是为了澄清;内联是JIT;不是C#编译器。
马克·格雷韦尔

还要注意,至少在最初,JIT甚至会在程序集边界上“更喜欢”内联静态方法。因此,一种古老的优化技术是将您的方法标记为静态。自那以来,这一直被社区所抵制,但是直到今天,我仍将选择内联方法,这些方法本质上是内部的,非多态的,并且通常比关联的堆栈填充+溢出花费更少。
肖恩·威尔逊

7

是确实,唯一的区别是它返回一个值。

简化(不使用表达式):

List<T>.ForEach 采取措施,不会期望返回结果。

因此,一个Action<T>代表就足够了..说:

List<T>.ForEach(param => Console.WriteLine(param));

等于说:

List<T>.ForEach(delegate(T param) { Console.WriteLine(param); });

区别在于,参数类型和委托声明是通过用法推断的,而在简单的内联方法中不需要括号。

在哪里

List<T>.Where 发挥作用,期待结果。

因此Function<T, bool>可以预期:

List<T>.Where(param => param.Value == SomeExpectedComparison);

与以下内容相同:

List<T>.Where(delegate(T param) { return param.Value == SomeExpectedComparison; });

您还可以内联声明这些方法并将它们分配给变量IE:

Action myAction = () => Console.WriteLine("I'm doing something Nifty!");

myAction();

要么

Function<object, string> myFunction = theObject => theObject.ToString();

string myString = myFunction(someObject);

我希望这有帮助。


2

在某些情况下,我确实希望强制插入代码。

例如,如果我有一个复杂的例程,其中在一个高度迭代的块中有大量决策,而这些决策会导致执行类似但略有不同的动作。例如,考虑一个复杂的(非数据库驱动的)排序比较器,其中排序算法根据许多不同的不相关标准对元素进行排序,例如,如果它们根据快速语言的语法和语义标准对单词进行排序,则可能会这样做识别系统。我倾向于编写辅助函数来处理这些操作,以保持源代码的可读性和模块化。

我知道这些辅助函数应该内联,因为如果人们不必理解代码,那么这就是编写代码的方式。在这种情况下,我当然想确保没有函数调用开销。


2

声明“最好不要理会这些事情,让编译器来完成工作。”(库迪·布罗里奇(Cody Brocious))是完全废话。我已经编写了高性能游戏代码已有20年了,但我还没有遇到一个“足够聪明”的编译器来知道应该内联哪些代码(函数)。在c#中使用“内联”语句将很有用,事实是,编译器只是不具备确定哪个函数应始终内联或不具有“内联”提示而始终内联所需的所有信息。当然,如果函数很小(访问器),那么它可能会自动内联,但是如果只有几行代码怎么办?毫无意义,编译器无法知道,您不能仅仅将其留给编译器以获取优化的代码(超出算法)。


3
“Nonesense,编译器没有办法知道”的JITer并实时剖析的函数调用..
布莱恩·戈登·

3
众所周知,.NET JIT在运行时进行的优化优于在编译时进行2遍静态分析的优化。“老派”编码人员难以掌握的部分是,JIT(而不是编译器)负责本机代码生成。JIT(而不是编译器)负责内联方法。
肖恩·威尔逊

1
对于我来说,可以编译您调用的代码而无需提供源代码,并且JIT可能选择内联该调用。通常,我会同意,但是我会说,作为人类,您将无法超越工具。
肖恩·威尔逊


0

不,C#中没有这样的构造,但是.NET JIT编译器可以决定在JIT时间进行内联函数调用。但是我实际上不知道它是否真的在做这样的优化。
(我认为应该:-))


0

如果您的程序集是ngen-ed,则可能需要查看TargetedPatchingOptOut。这将有助于ngen决定是否内联方法。MSDN参考

尽管它仍然只是优化的声明性提示,而不是命令性命令。


无论如何,JIT都会更好地知道是否内联。如果JIT无法优化调用站点,显然不会帮到您,但是为什么我还要担心仅调用几次的函数的最小开销呢?
Voo

-7

Lambda表达式是内联函数!我认为C#没有内联之类的额外属性!


9
我没有投票给您,但是请在发布随机答案之前先阅读问题并确保您理解它们。en.wikipedia.org/wiki/Inline_function
卡米洛·马丁

1
这个答案并不像事实那样糟糕-OP尚不清楚“函数内联”与“ lambda函数”,而且不幸的是,MSDN将lambda称为“内联语句”“内联代码”。但是,按照Konrad的回答,有一个属性可以向编译器提示有关内联方法的信息。
StuartLC

-7

C#不像python之类的动态语言那样支持内联方法(或函数)。但是,匿名方法和lambda可以用于类似目的,包括当您需要访问包含方法中的变量时(如下面的示例)。

static void Main(string[] args)
{
    int a = 1;

    Action inline = () => a++;
    inline();
    //here a = 2
}

10
与内联函数有什么关系?这是匿名方法。
Tomer W
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.