Heim  >  Artikel  >  Backend-Entwicklung  >  Detaillierte Einführung in den Beispielcode für die funktionale Programmierung in C#

Detaillierte Einführung in den Beispielcode für die funktionale Programmierung in C#

黄舟
黄舟Original
2017-03-09 15:17:391794Durchsuche

Wenn man von funktionaler Programmierung spricht, muss jeder an alte funktionale Sprachen wie LISP und Haskell denken, die über eine äußerst flexible und dynamische Syntax verfügen. In jüngerer Zeit sind Ruby, JavaScript und F# auch beliebte Sprachen für funktionale Programmierung. Da .net jedoch Lambda-Ausdrücke unterstützt, ist C# zwar eine zwingende Programmiersprache, steht aber in der funktionalen Programmierung nicht nach. Beim Schreiben von Code in C# werden wir Ideen wie Funktionen höherer Ordnung, kombinierte Funktionen und reines Funktions-Caching absichtlich oder unabsichtlich verwenden. Sogar Ideen wie Ausdrucksbäume stammen aus Ideen der funktionalen Programmierung. Als nächstes werden wir die häufig verwendeten funktionalen Programmierszenarien zusammenfassen, die uns helfen werden, diese Technologien flexibel im Programmierprozess anzuwenden, unsere Designideen zu erweitern und die Codequalität zu verbessern.

1. Funktionen höherer Ordnung

Laienhaft ausgedrückt: Funktionen höherer Ordnung: Eine Funktion, die eine Funktion als Parameter verwendet, wird als Funktion höherer Ordnung bezeichnet. Gemäß dieser Definition sind LINQ-Ausdrücke, Where, Select, SelectMany, First und andere in .net häufig verwendete Methoden allesamt Funktionen höherer Ordnung. Wann werden wir dieses Design verwenden, wenn wir unseren eigenen Code schreiben?

Beispiel: Entwerfen Sie eine Funktion zur Berechnung der Immobiliengebühren, var Gebühr=Quadrat*Preis, und die Fläche (Quadrat) wird je nach Art der Immobilie auf unterschiedliche Weise berechnet. Zivilwohnungen, Gewerbewohnungen usw. müssen mit unterschiedlichen Koeffizienten multipliziert werden. Entsprechend diesen Anforderungen versuchen wir, die folgende Funktion zu entwerfen:

Wohngebiet:

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

Gewerbe- und Wohngebiet:

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

Diese Funktionen haben alle eine gemeinsame Signatur: Func31803993a3bae4f7d54758d225c3e107, sodass wir diese Funktionssignatur verwenden können, um eine Funktion zur Berechnung von Immobiliengebühren zu entwerfen:

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

Ist das nicht ganz einfach? Schreiben Sie einen Test und sehen Sie sich

[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);
}

an 2. Faule Bewertung

C# verwendet bei der Ausführung eine strikte Auswertungsstrategie. Die sogenannte strikte Auswertung bedeutet, dass Parameter ausgewertet werden, bevor sie an die Funktion übergeben werden. Ist diese Erklärung noch etwas unklar? Schauen wir uns ein Szenario an: Es muss eine Aufgabe ausgeführt werden, die erfordert, dass die aktuelle Speichernutzung weniger als 80 % beträgt und das Ergebnis der vorherigen Berechnung

Wir können schnell C#-Code schreiben, der diese Anforderung erfüllt:

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");
     }
 }

Wenn Sie NextStep ausführen, müssen Sie die Speichernutzung und das Berechnungsergebnis des ersten Schritts übergeben (Funktion BigCalculationForFirstStep). Wie im Code gezeigt, ist der Vorgang des ersten Schritts aufgrund der strengen Bewertungsstrategie ein sehr zeitaufwändiger Vorgang von C#, für Anweisungen If (memoryUtilization0565af0030a0e9b90d6d2507f0bca1b2-Klasse können Sie diesen Mechanismus in Szenarien verwenden, in denen ein solcher Bedarf besteht 3. Funktion Curry

Currying wird auch Teilanwendung genannt. Definition: Es handelt sich um eine Technologie, die eine Funktion, die mehrere Parameter akzeptiert, in eine Funktion umwandelt, die einen einzelnen Parameter akzeptiert (den ersten Parameter der ursprünglichen Funktion), und eine neue Funktion zurückgibt, die die verbleibenden Parameter akzeptiert und ein Ergebnis zurückgibt. PS: Warum Ist die offizielle Erklärung so verwirrend?

Angesichts einer solchen Definition ist es wahrscheinlich für jeden schwer zu verstehen, was das ist. Beginnen wir also mit dem Curry-Prinzip:

​Schreiben Sie eine Funktion, die zwei Zahlen addiert:

OK, wie verwende ich diese Funktion?
public Func<int, int, int> AddTwoNumber()
{
    return (x, y) => x + y;
}

1+2=3, der Aufruf ist sehr einfach. Um unsere Anforderungen zu verbessern, benötigen wir eine Funktion, die die Eingabe eines Parameters (Zahl) erfordert und das Ergebnis aus 10 + dem Eingabeparameter (Zahl) berechnet. Ich denke, jemand wird sagen, dass der obige Code diese Anforderung vollständig erfüllen kann. Wenn Sie 10 als ersten Parameter übergeben, ist alles in Ordnung. Wenn Sie das glauben, habe ich nichts zu tun. Andere haben vielleicht gesagt, dass das Schreiben einer weiteren Überladung nur einen Parameter erfordert, aber die tatsächliche Situation ist nicht zulässig. Wir rufen die von anderen bereitgestellte API auf und können keine Überladungen hinzufügen. Es ist ersichtlich, dass das Verwendungsszenario der teilweisen Anwendung kein sehr häufiges Szenario ist. Daher besteht das beste Design darin, die entsprechende Technologie in die entsprechende Szene zu integrieren. Schauen wir uns die Implementierung der teilweisen Anwendung an:
var result= _curringReasoning.AddTwoNumber()(1,2);

Die Funktionssignatur, die durch den Ausdruck x => y => Function vom Typ int> erhalten wird. Wenn wir um diese Zeit noch einmal anrufen:
public Func<int, Func<int, int>> AddTwoNumberCurrying()
{
    Func<int, Func<int, int>> addCurrying = x => y => x + y;
    return addCurrying;
}
//Act
var curringResult = curringReasoning.AddTwoNumberCurrying()(10);
var result = curringResult(2);
 
//Assert
result.Should().Be(12);

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

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

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

  局部套用版本:

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;
}

  调用过程:

[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);
}

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

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);
}

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

  有了这些扩展方法,使用局部套用的时候就更加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);
}

  好了,局部套用就说到这里,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));
}

  使用:

[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);
}

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

Das obige ist der detaillierte Inhalt vonDetaillierte Einführung in den Beispielcode für die funktionale Programmierung in C#. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn