Home Backend Development C#.Net Tutorial Detailed introduction to C# functional programming sample code

Detailed introduction to C# functional programming sample code

Mar 09, 2017 pm 03:17 PM

When mentioning functional programming, everyone must think of ancient functional languages ​​such as LISP and Haskell, which have highly flexible and dynamic syntax. More recently, Ruby, JavaScript, and F# are also popular languages ​​for functional programming. However, since .net supports lambda expressions, although C# is an imperative programming language, it is not inferior in functional programming. In the process of writing code in C#, we will use ideas such as higher-order functions, combined functions, and pure function caching intentionally or unintentionally. Even ideas such as expression trees come from functional programming ideas. So next we will summarize the commonly used functional programming scenarios, which will help us flexibly apply these technologies in the programming process, expand our design ideas and improve code quality.

1. High-order functions

In layman's terms, higher-order functions: a function that uses a function as a parameter is called a higher-order function. According to this definition, LINQ expressions, Where, Select, SelectMany, First and other methods used extensively in .net are all high-order functions. So when will we use this design when we write our own code?

Example: Design a function to calculate property fees, var fee=square*price, and the area (square) is calculated in different ways depending on the nature of the property. Civilian residences, commercial residences, etc. need to be multiplied by different coefficients. According to such needs, we try to design the following function:

Residential area:

public Func<int,int,decimal> SquareForCivil()
{
    return (width,hight)=>width*hight;
}
Copy after login

Commercial residential area:

public Func<int, int, decimal> SquareForBusiness()
{
    return (width, hight) => width * hight*1.2m;
}
Copy after login

These functions all have a common signature: Func, so we can use this function signature to design a function for calculating property fees:

public decimal PropertyFee(decimal price,int width,int hight, Func<int, int, decimal> square)
{
    return price*square(width, hight);
}
Copy after login

Isn’t it very easy? Write a test and see

[Test]
public void Should_calculate_propertyFee_for_two_area()
{
    //Arrange
    var calculator = new PropertyFeeCalculator();
    //Act
    var feeForBusiness= calculator.PropertyFee(2m,2, 2, calculator.SquareForBusiness());
    var feeForCivil = calculator.PropertyFee(1m, 2, 2, calculator.SquareForCivil());
    //Assert
    feeForBusiness.Should().Be(9.6m);
    feeForCivil.Should().Be(4m);
}
Copy after login

2. Lazy evaluation

C# uses a strict evaluation strategy during execution. The so-called strict evaluation means that parameters are evaluated before being passed to the function. Is this explanation still a little unclear? Let's look at a scenario: there is a task that needs to be executed, which requires that the current memory usage is less than 80%, and the result of the previous calculation is <100. This condition can only be executed if this condition is met.

We can quickly write C# code that meets this requirement:

public double MemoryUtilization()
 {
     //计算目前内存使用率
     var pcInfo = new ComputerInfo();
     var usedMem = pcInfo.TotalPhysicalMemory - pcInfo.AvailablePhysicalMemory; 
     return (double)(usedMem / Convert.ToDecimal(pcInfo.TotalPhysicalMemory));
 }
 
 public int BigCalculatationForFirstStep()
 {
     //第一步运算
     System.Threading.Thread.Sleep(TimeSpan.FromSeconds(2));
     Console.WriteLine("big calulation");
     FirstStepExecuted = true;
     return 10;
 }
 
 public void NextStep(double memoryUtilization,int firstStepDistance)
 {
//下一步运算
     if(memoryUtilization<0.8&&firstStepDistance<100)
     {
         Console.WriteLine("Next step");
     }
 }
Copy after login

When executing NextStep, you need to pass in the memory usage and the calculation result of the first step (function BigCalculationForFirstStep). As shown in the code, the first step operation is a very time-consuming operation, but due to the strict evaluation strategy of C#, for statements If (memoryUtilization<0.8&&firstStepDistance<100), even if the memory usage is greater than 80%, the first step operation must be performed. Obviously, if the memory usage is greater than 80%, the value of firstStepDistance is no longer important, and it is completely fine. No need to calculate.

So lazy evaluation means that expressions or parts of expressions are evaluated only when their results are actually needed. We try to rewrite this requirement using higher-order functions:

public void NextStepWithOrderFunction(Func<double> memoryUtilization,Func<int> firstStep)
{
    if (memoryUtilization() < 0.8 && firstStep() < 100)
    {
        Console.WriteLine("Next step");
    }
}
Copy after login

The code is very simple, just use a function expression to replace the function value. If if (memoryUtilization() < 0.8.. is not satisfied, the following functions will not be executed. Microsoft added Lazy< in version .net4.0 ;T> class, you can use this mechanism in scenarios where there is such a need.

3. Function Curry

Currying is also called partial application. Definition: It is a technology that transforms a function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function), and returns a new function that accepts the remaining parameters and returns a result. PS: Why is the official explanation so confusing? mouth?

Seeing such a definition, it is probably difficult for everyone to understand what this is, so let’s start with the principle of curry:

Write a function that adds two numbers:

public Func<int, int, int> AddTwoNumber()
{
    return (x, y) => x + y;
}
Copy after login

OK, how to use this function?

var result= _curringReasoning.AddTwoNumber()(1,2);
Copy after login

1+2=3, the call is very simple. To upgrade our requirements, we need a function that requires the input of a parameter (number) and calculates the result of 10 + the input parameter (number). I guess someone has said that the above code can completely achieve this requirement. If you pass in 10 as the first parameter, it will be over. OK, if you think so, I have nothing to do. Others may have said that writing an overload requires only one parameter, but the actual situation is not allowed. We are calling an api provided by others and cannot add an overload. It can be seen that the usage scenario of partial application is not a very common scenario, so the best design is to match the appropriate technology in the appropriate scene. Let’s look at the implementation of partial application:

public Func<int, Func<int, int>> AddTwoNumberCurrying()
{
    Func<int, Func<int, int>> addCurrying = x => y => x + y;
    return addCurrying;
}
Copy after login

The function signature obtained by the expression x => y => Function of type int>. At this point if we call again:

//Act
var curringResult = curringReasoning.AddTwoNumberCurrying()(10);
var result = curringResult(2);
 
//Assert
result.Should().Be(12);
Copy after login

  这句话:var curringResult = curringReasoning.AddTwoNumberCurrying()(10); 生成的函数就是只接收一个参数(number),且可以计算出10+number的函数。

  同样的道理,三个数相加的函数:

public Func<int,int,int,int> AddThreeNumber()
{
    return (x, y, z) => x + y + z;
}
Copy after login

  局部套用版本:

public Func<int,Func<int,Func<int,int>>> AddThreeNumberCurrying()
{
    Func<int, Func<int, Func<int, int>>> addCurring = x => y => z => x + y + z;
    return addCurring;
}
Copy after login

  调用过程:

[Test]
public void Three_number_add_test()
{
    //Arrange
    var curringReasoning = new CurryingReasoning();
 
    //Act
    var result1 = curringReasoning.AddThreeNumber()(1, 2, 3);
    var curringResult = curringReasoning.AddThreeNumberCurrying()(1);
    var curringResult2 = curringResult(2);
    var result2 = curringResult2(3);
    
    //Assert
    result1.Should().Be(6);
    result2.Should().Be(6);
}
Copy after login

  当函数参数多了之后,手动局部套用越来越不容易写,我们可以利用扩展方法自动局部套用:

public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(this Func<T1, T2, TResult> func)
{
    return x => y => func(x, y);
}
 
public static Func<T1, Func<T2, Func<T3, TResult>>> Curry<T1, T2, T3, TResult>(this Func<T1, T2, T3,TResult> func)
{
    return x => y => z=>func(x, y,z);
}
Copy after login

  同样的道理,Action<>签名的函数也可以自动套用

  有了这些扩展方法,使用局部套用的时候就更加easy了

[Test]
public void Should_auto_curry_two_number_add_function()
{
    //Arrange
    var add = _curringReasoning.AddTwoNumber();
    var addCurrying = add.Curry();
 
    //Act
    var result = addCurrying(1)(2);
 
    //Assert
    result.Should().Be(3);
}
Copy after login

  好了,局部套用就说到这里,stackoverflow有几篇关于currying使用的场景和定义的文章,大家可以继续了解。

  函数式编程还有一些重要的思想,例如:纯函数的缓存,所为纯函数是指函数的调用不受外界的影响,相同的参数调用得到的值始终是相同的。尾递归,单子,代码即数据(.net中的表达式树),部分应用,组合函数,这些思想有的我也仍然在学习中,有的还在思考其最佳使用场景,所以不再总结,如果哪天领会了其思想会补充。

  四、设计案例

  最后我还是想设计一个场景,把高阶函数,lambda表达式,泛型方法结合在一起,我之所以设计这样的例子是因为现在很多的框架,开源的项目都有类似的写法,也正是因为各种技术和思想结合在一起,才有了极富有表达力并且非常优雅的代码。

  需求:设计一个单词查找器,该查找器可以查找某个传入的model的某些字段是否包含某个单词,由于不同的model具有不同的字段,所以该查找需要配置,并且可以充分利用vs的智能提示。

  这个功能其实就两个方法:

private readonly List<Func<string, bool>> _conditions; 
 
public WordFinder<TModel> Find<TProperty>(Func<TModel,TProperty> expression)
{
    Func<string, bool> searchCondition = word => expression(_model).ToString().Split(&#39; &#39;).Contains(word);
    _conditions.Add(searchCondition);
    return this;
}
 
public bool Execute(string wordList)
{
    return _conditions.Any(x=>x(wordList));
}
Copy after login

  使用:

[Test]
public void Should_find_a_word()
{
    //Arrange
    var article = new Article()
    {
        Title = "this is a title",
        Content = "this is content",
        Comment = "this is comment",
        Author = "this is author"
    };
 
    //Act
    var result = Finder.For(article)
        .Find(x => x.Title)
        .Find(x => x.Content)
        .Find(x => x.Comment)
        .Find(x => x.Author)
        .Execute( "content");
 
    //Assert
    result.Should().Be(true);
}
Copy after login

  该案例本身不具有实用性,但是大家可以看到,正是各种技术的综合应用才设计出极具语义的api, 如果函数参数改为Expression> 类型,我们还可以读取到具体的属性名称等信息。

The above is the detailed content of Detailed introduction to C# functional programming sample code. For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Active Directory with C# Active Directory with C# Sep 03, 2024 pm 03:33 PM

Guide to Active Directory with C#. Here we discuss the introduction and how Active Directory works in C# along with the syntax and example.

C# Serialization C# Serialization Sep 03, 2024 pm 03:30 PM

Guide to C# Serialization. Here we discuss the introduction, steps of C# serialization object, working, and example respectively.

Random Number Generator in C# Random Number Generator in C# Sep 03, 2024 pm 03:34 PM

Guide to Random Number Generator in C#. Here we discuss how Random Number Generator work, concept of pseudo-random and secure numbers.

C# Data Grid View C# Data Grid View Sep 03, 2024 pm 03:32 PM

Guide to C# Data Grid View. Here we discuss the examples of how a data grid view can be loaded and exported from the SQL database or an excel file.

Factorial in C# Factorial in C# Sep 03, 2024 pm 03:34 PM

Guide to Factorial in C#. Here we discuss the introduction to factorial in c# along with different examples and code implementation.

Patterns in C# Patterns in C# Sep 03, 2024 pm 03:33 PM

Guide to Patterns in C#. Here we discuss the introduction and top 3 types of Patterns in C# along with its examples and code implementation.

Prime Numbers in C# Prime Numbers in C# Sep 03, 2024 pm 03:35 PM

Guide to Prime Numbers in C#. Here we discuss the introduction and examples of prime numbers in c# along with code implementation.

The difference between multithreading and asynchronous c# The difference between multithreading and asynchronous c# Apr 03, 2025 pm 02:57 PM

The difference between multithreading and asynchronous is that multithreading executes multiple threads at the same time, while asynchronously performs operations without blocking the current thread. Multithreading is used for compute-intensive tasks, while asynchronously is used for user interaction. The advantage of multi-threading is to improve computing performance, while the advantage of asynchronous is to not block UI threads. Choosing multithreading or asynchronous depends on the nature of the task: Computation-intensive tasks use multithreading, tasks that interact with external resources and need to keep UI responsiveness use asynchronous.

See all articles