如何在C#中创建内联函数

134

我正在使用Linq To XML

new XElement("Prefix", Prefix == null ? "" : Prefix)

但我想在将其添加到XML之前对前缀进行一些计算,例如消除空格、特殊字符、某些计算等。

我不想创建函数,因为这些函数对我的程序的其他部分没有帮助,但是有没有创建内联函数的方法?


7
不要因为一个函数不会被其他地方调用而禁止自己创建它。函数也可以用来清理代码(如果您有一个跨越多页的大型过程,将其分解成函数可以使其更易读)并且自我说明(只需适当地命名函数即可向读者传达信息)。 - Joe
20
如果您从Google进入并想知道如何在C#中模拟内联函数,请转到此处: https://dev59.com/IHRB5IYBdhLWcg3w6LR2下面的答案并不涉及到“inline”函数的真正意义,而且C#甚至没有真正的内联函数,但上面提供了一个可以使用的编译器优化。 - JamesHoux
谢谢詹姆斯,问题标题确实有些误导。 - Zar Shardan
9
如果您是从谷歌搜索进入此页面,而且不想深入了解,请使用以下属性:[MethodImpl(MethodImplOptions.AggressiveInlining)] - BrainSlugs83
6个回答

295
是的,C#支持这个功能。有多种语法可供选择。
  • Anonymous methods were added in C# 2.0:

    Func<int, int, int> add = delegate(int x, int y)
    {
        return x + y;
    };
    Action<int> print = delegate(int x)
    {
        Console.WriteLine(x);
    }
    Action<int> helloWorld = delegate // parameters can be elided if ignored
    {
        Console.WriteLine("Hello world!");
    }
    
  • Lambdas are new in C# 3.0 and come in two flavours.

    • Expression lambdas:

      Func<int, int, int> add = (int x, int y) => x + y; // or...
      Func<int, int, int> add = (x, y) => x + y; // types are inferred by the compiler
      
    • Statement lambdas:

      Action<int> print = (int x) => { Console.WriteLine(x); };
      Action<int> print = x => { Console.WriteLine(x); }; // inferred types
      Func<int, int, int> add = (x, y) => { return x + y; };
      
  • Local functions have been introduced with C# 7.0:

    int add(int x, int y) => x + y;
    void print(int x) { Console.WriteLine(x); }
    
这些基本上有两种不同的类型:FuncActionFunc 返回值,但 Action 不返回。 Func 的最后一个类型参数是返回类型; 其他所有类型都是参数类型。
有类似的类型但名称不同,但内联声明的语法相同。 例如,Comparison<T> 就大致相当于 Func<T, T, int>
Func<string, string, int> compare1 = (l,r) => 1;
Comparison<string> compare2 = (l, r) => 1;
Comparison<string> compare3 = compare1; // this one only works from C# 4.0 onwards

这些可以像常规方法一样直接调用:

int x = add(23, 17); // x == 40
print(x); // outputs 40
helloWorld(x); // helloWorld has one int parameter declared: Action<int>
               // even though it does not make any use of it.

7
我不理解这个答案。在他的例子中,他想要内联声明new XElement("Prefix", prefix => newPrefix)。所有这些答案都等同于声明一个新函数。为什么不只是声明一个新函数呢? - Matthew James Davis
3
因为:
  1. 理解内联函数的方法比搜索不同声明要容易得多
  2. 使用内联函数时,通常需要编写较少的代码
- Volodymyr
1
我正在使用Unity3D,它在所有开箱即用的目的上都是C# <= 3.0,因此2.0+的历史是值得赞赏的。 - Jacksonkr

59

C# 7增加了对本地函数的支持:local functions

这是使用本地函数的先前示例:

void Method()
{
    string localFunction(string source)
    {
        // add your functionality here
        return source ;
    };

   // call the inline function
   localFunction("prefix");
}

1
那个函数会被创建多少次?它只会在我调用包装函数时创建一次吗? - qqqqqqq
内联函数可以是lambda函数吗? - qqqqqqq
1
从这个例子中可以得到肯定的答复 string localFunction(string source) => source;阅读章节 关于明确赋值和变量捕获,这应该能够回答你的问题。 - DalSoft
2
@c#是静态类型的,因此localfunction只会在编译时创建一次。对于Javascript来说,情况就不同了...每次调用Method()函数时都会创建该函数。 - enorl76

47
你的问题的答案是肯定的和否定的,这取决于你所指的“内联函数”的含义。如果你使用的是C++开发中使用的术语,那么答案是否定的,你不能这样做——即使是lambda表达式也是函数调用。虽然在C#中可以定义内联lambda表达式来替换函数声明,但编译器最终仍将创建一个匿名函数。
以下是我用来测试这个的一些非常简单的代码(VS2015):
    static void Main(string[] args)
    {
        Func<int, int> incr = a => a + 1;
        Console.WriteLine($"P1 = {incr(5)}");
    }

编译器生成什么?我使用了一个很棒的工具叫ILSpy,它可以展示实际生成的IL程序集。看一下(我省略了很多类设置的内容)

这是主函数:

        IL_001f: stloc.0
        IL_0020: ldstr "P1 = {0}"
        IL_0025: ldloc.0
        IL_0026: ldc.i4.5
        IL_0027: callvirt instance !1 class [mscorlib]System.Func`2<int32, int32>::Invoke(!0)
        IL_002c: box [mscorlib]System.Int32
        IL_0031: call string [mscorlib]System.String::Format(string, object)
        IL_0036: call void [mscorlib]System.Console::WriteLine(string)
        IL_003b: ret

看到IL_0026和IL_0027这两行吗? 这两条指令加载数字5并调用一个函数。 然后,IL_0031和IL_0036格式化并打印结果。

这里是被调用的函数:

        .method assembly hidebysig 
            instance int32 '<Main>b__0_0' (
                int32 a
            ) cil managed 
        {
            // Method begins at RVA 0x20ac
            // Code size 4 (0x4)
            .maxstack 8

            IL_0000: ldarg.1
            IL_0001: ldc.i4.1
            IL_0002: add
            IL_0003: ret
        } // end of method '<>c'::'<Main>b__0_0'

这是一个非常简短的函数,但它确实是一个函数。

这个函数值得优化吗?不必要。除非你每秒钟调用它数千次,但如果性能如此重要,那么你应该考虑调用用C/C++编写的本地代码来完成工作。

在我的经验中,可读性和可维护性几乎总是比为了争取几微秒的速度而进行的优化更加重要。使用函数使你的代码易读,并控制变量作用域,不要担心性能问题。

“过早的优化是所有恶之源(或者至少是大部分),在编程中。” -- 唐纳德·克努斯

“一个不能正确运行的程序不需要快速运行” -- 我


8
这是有关内联C#函数的热门谷歌搜索结果。你说:“也许如果你每秒调用它数千次”,那么,我每秒调用它400万次,所以……内联可能会很有用(并且极大地提高代码可读性,相对于我即将不得不执行的手动展开一个函数在16个位置上)。 - Adam
1
对于那些熟悉 Kotlin 的人来说,这意味着 C# 中没有与 Kotlin 的 "inline" 函数相对应的功能。 - Vapid

18

可以。

您可以创建匿名方法lambda表达式

Func<string, string> PrefixTrimmer = delegate(string x) {
    return x ?? "";
};
Func<string, string> PrefixTrimmer = x => x ?? "";

谢谢您的回答,能否再详细解释一下您的回答,特别是这个 lambda 表达式 codeFunc<string, string> PrefixTrimmer = x => x ?? "";code。我是新手,对 MSDN 文档并不是很理解。 - Joe Dixon
2
@Joe: x ?? "" 是空值合并运算符。http://msdn.microsoft.com/zh-cn/library/ms173224.aspx - SLaks
x => (something) 中,x 是一个参数,(something) 是返回值。 - SLaks
-1. http://zh.wikipedia.org/wiki/%E5%86%85%E8%81%94%E5%87%BD%E6%95%B0 http://zh.wikipedia.org/wiki/%E5%B5%8C%E5%A5%97%E5%87%BD%E6%95%B0 - AK_
2
@Hellfrost:请仔细阅读问题。他并不是在询问C++风格的内联函数。我不想创建函数,因为这些函数对程序的其他部分没有任何帮助。 - SLaks
我理解他想要什么(嵌套函数)。他需要的是一个私有方法,主要是为了清晰和简洁。 - AK_

6
不仅可以在方法内部使用,还可以在类内部使用。
class Calculator
    {
        public static int Sum(int x,int y) => x + y;
        public static Func<int, int, int>  Add = (x, y) => x + y;
        public static Action<int,int> DisplaySum = (x, y) => Console.WriteLine(x + y);
    }

6
你可以使用 Func,它封装了一个具有一个参数并返回由 TResult 参数指定的类型值的方法。
void Method()
{
    Func<string,string> inlineFunction = source => 
    {
        // add your functionality here
        return source ;
     };


    // call the inline function
   inlineFunction("prefix");
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接