ZetCode

C# Func

最后修改于 2025 年 4 月 22 日

本教程探讨 C# 中的 Func 委托,它是函数式编程的强大工具,能够实现简洁而富有表现力的代码。

与具有一流函数的语言不同,C# 依赖于像 Func 这样的委托来模拟函数式编程。与 lambda 表达式结合使用,Func 可以减少冗长并增强代码灵活性,如在 LINQ 和其他场景中所见。

C# Func

Func 是一种内置的泛型委托类型,与 PredicateAction 并列。它支持方法、匿名方法或 lambda 表达式,在函数式编程中提供了多功能性。

Func 支持 0 到 16 个输入参数,并且需要返回类型。凭借 16 个重载,它可以适应各种方法签名。

public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

此委托表示具有两个输入参数且返回类型为 TResult 的方法。

C# Func 简单示例

此示例演示了 Func 委托的基本用法。

Program.cs
string GetMessage()
{
    return "Hello there!";
}

Func<string> sayHello = GetMessage;
Console.WriteLine(sayHello());

该示例使用一个没有参数的 Func 委托,从方法返回一个字符串。

string GetMessage()
{
    return "Hello there!";
}

GetMessage 方法被 Func 委托引用。

Func<string> sayHello = GetMessage;

委托被分配为引用 GetMessage,简化了方法调用。

Console.WriteLine(sayHello());

委托调用该方法,打印结果。

$ dotnet run
Hello there!

C# Func 示例

此示例使用 Func 执行加法。

Program.cs
int Sum(int x, int y)
{
    return x + y;
}

Func<int, int, int> add = Sum;

int res = add(150, 10);

Console.WriteLine(res);

通过 Func 委托引用 Sum 方法以添加两个整数。

Func<int, int, int> add = Sum;

委托接受两个整数并返回它们的总和。

$ dotnet run
160

此示例将 Func 扩展到三个参数。

Program.cs
int Sum(int x, int y, int z)
{
    return x + y + z;
}

Func<int, int, int, int> add = Sum;

int res = add(150, 20, 30);

Console.WriteLine(res);

委托引用一个对三个整数求和的方法。

$ dotnet run
200

此示例显示了一个没有 Func 的自定义委托。

Program.cs
int Sum(int x, int y)
{
    return x + y;
}

Add AddTwo = Sum;
int res = AddTwo(150, 10);

Console.WriteLine(res);

delegate int Add(int x, int y);

使用自定义委托代替 Func,说明了所需的额外样板代码。

C# Func 与 lambda 表达式

Lambda 表达式使用 => 运算符简化 Func 的创建,从而增强了代码的简洁性。

Program.cs
Func<int, int, int> randInt = (n1, n2) => new Random().Next(n1, n2);
Console.WriteLine(randInt(1, 100));

该示例使用基于 lambda 的 Func 在一定范围内生成一个随机整数。

C# Func Linq Where

许多 LINQ 方法(如 Where)接受 Func 委托,以根据谓词过滤序列。

Program.cs
Func<string, bool> HasThree = str => str.Length == 3;

string[] words =
[
    "sky", "forest", "wood", "cloud", "falcon", "owl" , "ocean",
    "water", "bow", "tiny", "arc"
];

IEnumerable<string> threeLetterWords = words.Where(HasThree);

foreach (var word in threeLetterWords)
{
    Console.WriteLine(word);
}

该示例使用基于 Func 的谓词过滤恰好包含三个字母的单词。

Func<string, bool> HasThree = str => str.Length == 3;

Lambda 表达式检查字符串的长度是否为三,返回一个布尔值。

IEnumerable threeLetterWords = words.Where(HasThree);

Where 方法应用谓词来过滤数组。

$ dotnet run
sky
owl
bow
arc

C# Func 委托列表

可以将 Func 委托存储在集合中,以进行动态函数应用。

Program.cs
int[] vals = [1, 2, 3, 4, 5];

Func<int, int> square = x => x * x;
Func<int, int> cube = x => x * x * x;
Func<int, int> inc = x => x + 1;

List<Func<int, int>> fns =
[
    inc, square, cube
];

foreach (var fn in fns)
{
    var res = vals.Select(fn);

    Console.WriteLine(string.Join(", ", res));
}

该示例将来自列表的多个 Func 委托应用于数组,演示了动态转换。

$ dotnet run
2, 3, 4, 5, 6
1, 4, 9, 16, 25
1, 8, 27, 64, 125

C# Func 过滤数组

此示例使用 Func 委托过滤用户对象数组。

Program.cs
User[] users =
[
  new (1, "John", "London", "2001-04-01"),
  new (2, "Lenny", "New York", "1997-12-11"),
  new (3, "Andrew", "Boston", "1987-02-22"),
  new (4, "Peter", "Prague", "1936-03-24"),
  new (5, "Anna", "Bratislava", "1973-11-18"),
  new (6, "Albert", "Bratislava", "1940-12-11"),
  new (7, "Adam", "Trnava", "1983-12-01"),
  new (8, "Robert", "Bratislava", "1935-05-15"),
  new (9, "Robert", "Prague", "1998-03-14"),
];

var city = "Bratislava";
Func<User, bool> livesIn = e => e.City == city;

var res = users.Where(livesIn);

foreach (var e in res)
{
    Console.WriteLine(e);
}

record User(int Id, string Name, string City, string DateOfBirth);

该程序使用 Func 谓词过滤居住在布拉迪斯拉发的用户。

var city = "Bratislava";
Func livesIn = e => e.City == city;

谓词检查用户的城市是否与“Bratislava”匹配。

var res = users.Where(livesIn);

Where 方法应用谓词来过滤数组。

$ dotnet run
User { Id = 5, Name = Anna, City = Bratislava, DateOfBirth = 1973-11-18 }
User { Id = 6, Name = Albert, City = Bratislava, DateOfBirth = 1940-12-11 }
User { Id = 8, Name = Robert, City = Bratislava, DateOfBirth = 1935-05-15 }

C# Func 按年龄过滤

此示例使用 Func 委托按年龄过滤用户。

Program.cs
List<User> users =
[
  new (1, "John", "London", "2001-04-01"),
  new (2, "Lenny", "New York", "1997-12-11"),
  new (3, "Andrew", "Boston", "1987-02-22"),
  new (4, "Peter", "Prague", "1936-03-24"),
  new (5, "Anna", "Bratislava", "1973-11-18"),
  new (6, "Albert", "Bratislava", "1940-12-11"),
  new (7, "Adam", "Trnava", "1983-12-01"),
  new (8, "Robert", "Bratislava", "1935-05-15"),
  new (9, "Robert", "Prague", "1998-03-14"),
];

var age = 60;
Func<User, bool> olderThan = e => GetAge(e) > age;

var res = users.Where(olderThan);

foreach (var e in res)
{
    Console.WriteLine(e);
}

int GetAge(User user)
{
    var dob = DateTime.Parse(user.DateOfBirth);
    return (int)Math.Floor((DateTime.Now - dob).TotalDays / 365.25D);
}

record User(int Id, string Name, string City, string DateOfBirth);

该程序使用 Func 委托过滤 60 岁以上的用户。

Func<User, bool> olderThan = e => GetAge(e) > age;

谓词使用 GetAge 来计算和比较用户年龄。

var res = users.Where(olderThan);

Where 方法根据谓词过滤用户。

int GetAge(User user)
{
    var dob = DateTime.Parse(user.DateOfBirth);
    return (int) Math.Floor((DateTime.Now - dob).TotalDays / 365.25D);
}

GetAge 方法从用户的出生日期计算用户的年龄。

$ dotnet run
User { Id = 4, Name = Peter, City = Prague, DateOfBirth = 1936-03-24 }
User { Id = 6, Name = Albert, City = Bratislava, DateOfBirth = 1940-12-11 }
User { Id = 8, Name = Robert, City = Bratislava, DateOfBirth = 1935-05-15 }

C# 委托谓词

Predicate 是一个专门的 Func,它返回一个布尔值,用于单参数谓词。

所有 Predicate 功能都可以通过 Func 实现,但 Predicate 提供了更清晰的意图。

Program.cs
User[] users =
[
  new (1, "John", "London", "2001-04-01"),
  new (2, "Lenny", "New York", "1997-12-11"),
  new (3, "Andrew", "Boston", "1987-02-22"),
  new (4, "Peter", "Prague", "1936-03-24"),
  new (5, "Anna", "Bratislava", "1973-11-18"),
  new (6, "Albert", "Bratislava", "1940-12-11"),
  new (7, "Adam", "Trnava", "1983-12-01"),
  new (8, "Robert", "Bratislava", "1935-05-15"),
  new (9, "Robert", "Prague", "1998-03-14"),
];

var age = 60;
Predicate<User> olderThan = e => GetAge(e) > age;

var res = Array.FindAll(users, olderThan);

foreach (var e in res)
{
    Console.WriteLine(e);
}

int GetAge(User user)
{
    var dob = DateTime.Parse(user.DateOfBirth);
    return (int) Math.Floor((DateTime.Now - dob).TotalDays / 365.25D);
}

record User(int Id, string Name, string City, string DateOfBirth);

该示例使用 Predicate 查找 60 岁以上的用户。

Predicate olderThan = e => GetAge(e) > age;

Predicate 隐式返回一个布尔值,简化了签名。

var res = Array.FindAll(users, olderThan);

Array.FindAll 方法过滤与谓词匹配的元素。

int GetAge(User user)
{
    var dob = DateTime.Parse(user.DateOfBirth);
    return (int) Math.Floor((DateTime.Now - dob).TotalDays / 365.25D);
}

GetAge 方法计算用户的年龄。

$ dotnet run
User { Id = 4, Name = Peter, City = Prague, DateOfBirth = 1936-03-24 }
User { Id = 6, Name = Albert, City = Bratislava, DateOfBirth = 1940-12-11 }
User { Id = 8, Name = Robert, City = Bratislava, DateOfBirth = 1935-05-15 }

C# 将 Func 作为参数传递

此示例将 Func 委托作为方法参数传递。

Program.cs
List<Person> data =
[
    new ("John Doe", "gardener"),
    new ("Robert Brown", "programmer"),
    new ("Lucia Smith", "teacher"),
    new ("Thomas Neuwirth", "teacher")
];

ShowOutput(data, r => r.Occupation == "teacher");

void ShowOutput(List<Person> list, Func<Person, bool> condition)
{
    var data = list.Where(condition);

    foreach (var person in data)
    {
        Console.WriteLine("{0}, {1}", person.Name, person.Occupation);
    }
}

record Person(string Name, string Occupation);

ShowOutput 方法基于 Func 谓词过滤和显示人员。

void ShowOutput(List<Person> list, Func<Person, bool> condition)

该方法接受一个 Func 委托来动态过滤列表。

$ dotnet run
Lucia Smith, teacher
Thomas Neuwirth, teacher

C# Func 组合

此示例演示了通过链接转换来组合 Func 委托。

Program.cs
int[] vals = [1, 2, 3, 4, 5];

Func<int, int> inc = e => e + 1;
Func<int, int> cube = e => e * e * e;

var res = vals.Select(inc).Select(cube);

foreach (var e in res)
{
    Console.WriteLine(e);
}

该示例链接 Func 委托以递增和立方数组元素。

Func<int, int> inc = e => e + 1;
Func<int, int> cube = e => e * e * e;

两个 Func 委托定义递增和立方运算。

var res = vals.Select(inc).Select(cube);

Select 方法链接转换,按顺序应用它们。

$ dotnet run
8
27
64
125
216

C# Func 与异步方法

此示例将 Func 与异步方法一起使用以模拟延迟计算。

Program.cs
Func<Task<int>> getRandomAsync = async () =>
{
    await Task.Delay(1000); // Simulate async work
    return new Random().Next(1, 100);
};

int result = await getRandomAsync();
Console.WriteLine($"Random number: {result}");

该程序定义一个 Func,它返回一个 Task<int>,在延迟后生成一个随机数。

C# Func 与 LINQ 聚合

此示例将 Func 与 LINQ 一起使用以计算聚合值。

Program.cs
int[] numbers = [10, 20, 30, 40, 50];

Func<int, int> doubleValue = x => x * 2;
var sum = numbers.Select(doubleValue).Sum();

Console.WriteLine($"Sum of doubled values: {sum}");

该程序使用 Func 将每个数字加倍,并使用 LINQ 的 Sum 方法计算总和。

C# Func 与动态参数

此示例展示了如何在 C# 中基于运行时条件动态构造 Func 委托。 Func 委托提供的灵活性允许开发人员创建可重用、封装的逻辑,这些逻辑可以作为参数传递或根据应用程序的需求动态分配。

Program.cs
string[] words = { "apple", "banana", "cherry", "date" };

string filterType = "long"; // Can be "short" or "long"
Func<string, bool> filter = filterType == "long"
    ? s => s.Length > 5
    : s => s.Length <= 5;

var filteredWords = words.Where(filter);
foreach (var word in filteredWords)
{
    Console.WriteLine(word);
}

在此程序中,Func<T, TResult> 委托用于创建一个谓词函数,该函数根据单词的长度过滤单词数组。 Func<T, TResult> 委托表示一个方法,该方法接受类型 T 的单个输入并返回类型 TResult 的结果。 在这里,输入类型是 string,结果类型是 bool,这使得 Func 适合于定义一个评估为 true 或 false 的条件。

filterType 变量确定过滤函数的行为。 如果 filterType 的值设置为“long”,则程序会将 lambda 表达式 s => s.Length > 5 分配给 filter 变量。 此 lambda 检查每个单词的长度是否大于 5。 相反,如果 filterType 的值为“short”,则会分配另一个 lambda 表达式 s => s.Length <= 5,它会检查单词长度是否为 5 或更小。

然后,动态分配的 Func 作为谓词传递给 IEnumerable<T> 接口的 Where 方法。 Where 方法使用 filter 中定义的条件过滤单词序列。 这将生成一个仅包含满足指定条件的单词的集合。 最后,迭代过滤后的单词并将其打印到控制台,使用户可以查看动态过滤的输出。

C# Func 与错误处理

此示例演示了如何在 C# 的 Func 委托中包含错误处理。 通过将 try-catch 块直接嵌入到 lambda 表达式中,该程序即使在面对无效或意外输入时也能确保稳健可靠的处理。 这种方法在处理动态数据(例如用户输入或外部数据源)时特别有用,在这种情况下,更容易发生错误。

Program.cs
Func<string, int> parseNumber = s =>
{
    try
    {
        return int.Parse(s);
    }
    catch (FormatException)
    {
        Console.WriteLine($"Error: '{s}' is not a valid number");
        return 0;
    }
};

string[] inputs = { "123", "abc", "456" };
var results = inputs.Select(parseNumber);

foreach (var result in results)
{
    Console.WriteLine($"Result: {result}");
}

此程序中的 Func<string, int> 委托用于定义一个用于将字符串解析为整数的 lambda 表达式。 它封装了将数字的字符串表示形式转换为其整数等效形式的逻辑,同时优雅地处理潜在的异常。

错误处理是通过 lambda 表达式中的 try-catch 块集成的。 如果由于格式无效(例如,非数字字符)而无法解析输入字符串,则会引发 FormatExceptioncatch 块捕获此异常并通过打印指定无效输入的错误消息来向用户提供有意义的反馈。 lambda 表达式不会使程序崩溃,而是返回默认值 0,从而允许应用程序继续无缝处理其他输入。

IEnumerable<T> 接口的 Select 方法将 Func<string, int> 委托应用于 inputs 数组的每个元素。 这会生成一个整数集合,其中有效数字被转换,无效输入被替换为默认值 0。 然后使用 foreach 循环迭代过滤后的结果,并将每个解析后的值显示在控制台上,确保可以查看成功和失败的解析尝试。

此实现在无法保证数据完整性的情况下特别有用,例如表单中的用户输入值或从文件、API 或数据库等外部来源检索的数据。

来源

Func 委托

本文探讨了 C# Func 委托的高级用法,包括 LINQ、异步方法和错误处理。

作者

我是 Jan Bodnar,一位拥有丰富软件开发经验的敬业程序员。 自 2007 年以来,我撰写了超过 1,400 篇编程文章和八本电子书。 凭借十多年的编程教学经验,我通过全面的教程分享我的专业知识。

列出所有 C# 教程