【轉】背後的故事之 - 快樂的Lambda表達式(一)

来源:https://www.cnblogs.com/RYouHoo-923/archive/2018/01/12/8275566.html
-Advertisement-
Play Games

快樂的Lambda表達式(二) 自從Lambda隨.NET Framework3.5出現在.NET開發者眼前以來,它已經給我們帶來了太多的欣喜。它優雅,對開發者更友好,能提高開發效率,天啊!它還有可能降低發生一些潛在錯誤的可能。LINQ包括ASP.NET MVC中的很多功能都是用Lambda實現的。 ...


快樂的Lambda表達式(二)

  自從Lambda隨.NET Framework3.5出現在.NET開發者眼前以來,它已經給我們帶來了太多的欣喜。它優雅,對開發者更友好,能提高開發效率,天啊!它還有可能降低發生一些潛在錯誤的可能。LINQ包括ASP.NET MVC中的很多功能都是用Lambda實現的。我只能說自從用了Lambda,我腰也不酸了,腿也不疼了,手指也不抽筋了,就連寫代碼bug都少了。小伙伴們,你們今天用Lambda了麽?但是你真的瞭解它麽?今天我們就來好好的認識一下吧。

  本文會介紹到一些Lambda的基礎知識,然後會有一個小小的性能測試對比Lambda表達式和普通方法的性能,接著我們會通過IL來深入瞭解Lambda到底是什麼,最後我們將用Lambda表達式來實現一些JavaScript裡面比較常見的模式。

瞭解Lambda     

  在.NET 1.0的時候,大家都知道我們經常用到的是委托。有了委托呢,我們就可以像傳遞變數一樣的傳遞方法。在一定程式上來講,委托是一種強類型的托管的方法指針,曾經也一時被我們用的那叫一個廣泛呀,但是總的來說委托使用起來還是有一些繁瑣。來看看使用一個委托一共要以下幾個步驟:

  1. 用delegate關鍵字創建一個委托,包括聲明返回值和參數類型
  2. 使用的地方接收這個委托
  3. 創建這個委托的實例並指定一個返回值和參數類型匹配的方法傳遞過去

  複雜嗎?好吧,也許06年你說不複雜,但是現在,真的挺複雜的。

  後來,幸運的是.NET 2.0為了們帶來了泛型。於是我們有了泛型類,泛型方法,更重要的是泛型委托。最終 在.NET3.5的時候,我們Microsoft的兄弟們終於意識到其實我們只需要2個泛型委托(使用了重載)就可以覆蓋99%的使用場景了。

  • Action 沒有輸入參數和返回值的泛型委托
  • Action<T1, …, T16> 可以接收1個到16個參數的無返回值泛型委托
  • Func<T1, …, T16, Tout> 可以接收0到16個參數並且有返回值的泛型委托

  這樣我們就可以跳過上面的第一步了,不過第2步還是必須的,只是用Action或者Func替換了。別忘了在.NET2.0的時候我們還有匿名方法,雖然它沒怎麼流行起來,但是我們也給它 一個露臉的機會。

Func<double, double> square = delegate (double x) {
	return x * x;
}

  最後,終於輪到我們的Lambda優雅的登場了。

// 編譯器不知道後面到底是什麼玩意,所以我們這裡不能用var關鍵字
Action dummyLambda = () => { Console.WriteLine("Hello World from a Lambda expression!"); };

// double y = square(25);
Func<double, double> square = x => x * x;

// double z = product(9, 5);
Func<double, double, double> product = (x, y) => x * y;

// printProduct(9, 5);
Action<double, double> printProduct = (x, y) => { Console.WriteLine(x * y); };

// var sum = dotProduct(new double[] { 1, 2, 3 }, new double[] { 4, 5, 6 });
Func<double[], double[], double> dotProduct = (x, y) =>
{
    var dim = Math.Min(x.Length, y.Length);
    var sum = 0.0;
    for (var i = 0; i != dim; i++)
        sum += x[i] + y[i];
    return sum;
};

// var result = matrixVectorProductAsync(...);
Func<double, double, Task<double>> matrixVectorProductAsync = async (x, y) =>
{
    var sum = 0.0;
    /* do some stuff using await ... */
    return sum;
};

 

  從上面的代碼中我們可以看出:

  • 如果只有一個參數,不需要寫()
  • 如果只有一條執行語句,並且我們要返回它,就不需要{},並且不用寫return
  • Lambda可以非同步執行,只要在前面加上async關鍵字即可
  • Var關鍵字在大多數情況下都不能使用

  當然,關於最後一條,以下這些情況下我們還是可以用var關鍵字的。原因很簡單,我們告訴編譯器,後面是個什麼類型就可以了。

Func<double,double> square = (double x) => x * x;

Func<string,int> stringLengthSquare = (string s) => s.Length * s.Length;

Action<decimal,string> squareAndOutput = (decimal x, string s) =>
{
    var sqz = x * x;
    Console.WriteLine("Information by {0}: the square of {1} is {2}.", s, x, sqz);
};

  現在,我們已經知道Lambda的一些基本用法了,如果僅僅就這些東西,那就不叫快樂的Lambda表達式了,讓我們看看下麵的代碼。

var a = 5;
Func<int,int> multiplyWith = x => x * a;
var result1 = multiplyWith(10); //50
a = 10;
var result2 = multiplyWith(10); //100

  是不是有一點感覺了?我們可以在Lambda表達式中用到外面的變數,沒錯,也就是傳說中的閉包啦。

void DoSomeStuff()
{
    var coeff = 10;
    Func<int,int> compute = x => coeff * x;
    Action modifier = () =>
    {
        coeff = 5;
    };

    var result1 = DoMoreStuff(compute);

    ModifyStuff(modifier);

    var result2 = DoMoreStuff(compute);
}

int DoMoreStuff(Func<int,int> computer)
{
    return computer(5);
}

void ModifyStuff(Action modifier)
{
    modifier();
}

  在上面的代碼中,DoSomeStuff方法裡面的變數coeff實際是由外部方法ModifyStuff修改的,也就是說ModifyStuff這個方法擁有了訪問DoSomeStuff裡面一個局部變數的能力。它是如何做到的?我們馬上會說的J。當然,這個變數作用域的問題也是在使用閉包時應該註意的地方,稍有不慎就有可能會引發你想不到的後果。看看下麵這個你就知道了。

var buttons = new Button[10];

for (var i = 0; i < buttons.Length; i++)
{
    var button = new Button();
    button.Text = (i + 1) + ". Button - Click for Index!";
    button.OnClick += (s, e) => { Messagebox.Show(i.ToString()); };
    buttons[i] = button;
}

  猜猜你點擊這些按鈕的結果是什麼?是”1, 2, 3…”。但是,其實真正的結果是全部都顯示10。為什麼?不明覺歷了吧?那麼如果避免這種情況呢?

var button = new Button();
var index = i;
button.Text = (i + 1) + ". Button - Click for Index!";
button.OnClick += (s, e) => { Messagebox.Show(index.ToString()); };
buttons[i] = button;

  其實做法很簡單,就是在for的迴圈裡面把當前的i保存下來,那麼每一個表達式裡面存儲的值就不一樣了。

  接下來,我們整點高級的貨,和Lambda息息相關的表達式(Expression)。為什麼說什麼息息相關,因為我們可以用一個Expression將一個Lambda保存起來。並且允許我們在運行時去解釋這個Lambda表達式。來看一下下麵簡單的代碼:

Expression<Func<MyModel, int>> expr = model => model.MyProperty;
var member = expr.Body as MemberExpression;
var propertyName = member.Expression.Member.Name; 

  這個的確是Expression最簡單的用法之一,我們用expr存儲了後面的表達式。編譯器會為我們生成表達式樹,在表達式樹中包括了一個元數據像參數的類型,名稱還有方法體等等。在LINQ TO SQL中就是通過這種方法將我們設置的條件通過where擴展方法傳遞給後面的LINQ Provider進行解釋的,而LINQ Provider解釋的過程實際上就是將表達式樹轉換成SQL語句的過程。

Lambda表達式的性能

  關於Lambda性能的問題,我們首先可能會問它是比普通的方法快呢?還是慢呢?接下來我們就來一探究竟。首先我們通過一段代碼來測試一下普通方法和Lambda表達 式之間的性能差異。

class StandardBenchmark : Benchmark
{
    const int LENGTH = 100000;
    static double[] A;
    static double[] B;

    static void Init()
    {
        var r = new Random();
        A = new double[LENGTH];
        B = new double[LENGTH];

        for (var i = 0; i < LENGTH; i++)
        {
            A[i] = r.NextDouble();
            B[i] = r.NextDouble();
        }
    }

    static long LambdaBenchmark()
    {
        Func<double> Perform = () =>
        {
            var sum = 0.0;

            for (var i = 0; i < LENGTH; i++)
                sum += A[i] * B[i];

            return sum;
        };
        var iterations = new double[100];
        var timing = new Stopwatch();
        timing.Start();

        for (var j = 0; j < iterations.Length; j++)
            iterations[j] = Perform();

        timing.Stop();
        Console.WriteLine("Time for Lambda-Benchmark: \t {0}ms", timing.ElapsedMilliseconds);
        return timing.ElapsedMilliseconds;
    }

    static long NormalBenchmark()
    {
        var iterations = new double[100];
        var timing = new Stopwatch();
        timing.Start();

        for (var j = 0; j < iterations.Length; j++)
            iterations[j] = NormalPerform();

        timing.Stop();
        Console.WriteLine("Time for Normal-Benchmark: \t {0}ms", timing.ElapsedMilliseconds);
        return timing.ElapsedMilliseconds;
    }

    static double NormalPerform()
    {
        var sum = 0.0;

        for (var i = 0; i < LENGTH; i++)
            sum += A[i] * B[i];

        return sum;
    }
}
}

  代碼很簡單,我們通過執行同樣的代碼來比較,一個放在Lambda表達式里,一個放在普通的方法裡面。通過4次測試得到如下結果:

  Lambda  Normal-Method

  70ms  84ms
  73ms  69ms
  92ms  71ms
  87ms  74ms

  按理來說,Lambda應該是要比普通方法慢很小一點點的,但是不明白第一次的時候為什麼Lambda會比普通方法還快一點。- -!不過通過這樣的對比我想至少可以說明Lambda和普通方法之間的性能其實幾乎是沒有區別的。  

  那麼Lambda在經過編譯之後會變成什麼樣子呢?讓LINQPad告訴你。

  上圖中的Lambda表達式是這樣的:

Action<string> DoSomethingLambda = (s) =>
{
	Console.WriteLine(s);// + local
};

  對應的普通方法的寫法是這樣的:

void DoSomethingNormal(string s)
{
	Console.WriteLine(s);
}

  上面兩段代碼生成的IL代碼呢?是這樣地:

DoSomethingNormal:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  call        System.Console.WriteLine
IL_0007:  nop         
IL_0008:  ret         
<Main>b__0:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  call        System.Console.WriteLine
IL_0007:  nop         
IL_0008:  ret       

  最大的不同就是方法的名稱以及方法的使用而不是聲明,聲明實際上是一樣的。通過上面的IL代碼我們可以看出,這個表達式實際被編譯器取了一個名稱,同樣被放在了當前的類裡面。所以實際上,和我們調類裡面的方法沒有什麼兩樣。下麵這張圖說明瞭這個編譯的過程:

  上面的代碼中沒有用到外部變數,接下來我們來看另外一個例子。

void Main()
{
	int local = 5;

	Action<string> DoSomethingLambda = (s) => {
		Console.WriteLine(s + local);
	};
	
	global = local;
	
	DoSomethingLambda("Test 1");
	DoSomethingNormal("Test 2");
}

int global;

void DoSomethingNormal(string s)
{
	Console.WriteLine(s + global);
}

  這次的IL代碼會有什麼不同麽?

IL_0000:  newobj      UserQuery+<>c__DisplayClass1..ctor
IL_0005:  stloc.1     
IL_0006:  nop         
IL_0007:  ldloc.1     
IL_0008:  ldc.i4.5    
IL_0009:  stfld       UserQuery+<>c__DisplayClass1.local
IL_000E:  ldloc.1     
IL_000F:  ldftn       UserQuery+<>c__DisplayClass1.<Main>b__0
IL_0015:  newobj      System.Action<System.String>..ctor
IL_001A:  stloc.0     
IL_001B:  ldarg.0     
IL_001C:  ldloc.1     
IL_001D:  ldfld       UserQuery+<>c__DisplayClass1.local
IL_0022:  stfld       UserQuery.global
IL_0027:  ldloc.0     
IL_0028:  ldstr       "Test 1"
IL_002D:  callvirt    System.Action<System.String>.Invoke
IL_0032:  nop         
IL_0033:  ldarg.0     
IL_0034:  ldstr       "Test 2"
IL_0039:  call        UserQuery.DoSomethingNormal
IL_003E:  nop         

DoSomethingNormal:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  ldarg.0     
IL_0003:  ldfld       UserQuery.global
IL_0008:  box         System.Int32
IL_000D:  call        System.String.Concat
IL_0012:  call        System.Console.WriteLine
IL_0017:  nop         
IL_0018:  ret         

<>c__DisplayClass1.<Main>b__0:
IL_0000:  nop         
IL_0001:  ldarg.1     
IL_0002:  ldarg.0     
IL_0003:  ldfld       UserQuery+<>c__DisplayClass1.local
IL_0008:  box         System.Int32
IL_000D:  call        System.String.Concat
IL_0012:  call        System.Console.WriteLine
IL_0017:  nop         
IL_0018:  ret         

<>c__DisplayClass1..ctor:
IL_0000:  ldarg.0     
IL_0001:  call        System.Object..ctor
IL_0006:  ret      

  你發現了嗎?兩個方法所編譯出來的內容是一樣的, DoSomtingNormal和<>c__DisplayClass1.<Main>b__0,它們裡面的內容是一樣的。但是最大的不一樣,請註意了。當我們的Lambda表達式裡面用到了外部變數的時候,編譯器會為這個Lambda生成一個類,在這個類中包含了我們表達式方法。在使用這個Lambda表達式的地方呢,實際上是new了這個類的一個實例進行調用。這樣的話,我們表達式裡面的外部變數,也就是上面代碼中用到的local實際上是以一個全局變數的身份存在於這個實例中的。

用Lambda表達式實現一些在JavaScript中流行的模式

  說到JavaScript,最近幾年真是風聲水起。不光可以應用所有我們軟體工程現存的一些設計模式,並且由於它的靈活性,還有一些由於JavaScript特性而產生的模式。比如說模塊化,立即執行方法體等。.NET由於是強類型編譯型的語言,靈活性自然不如JavaScript,但是這並不意味著JavaScript能做的事情.NET就不能做,下麵我們就來實現一些JavaScript中好玩的寫法。

回調模式

  回調模式也並非JavaScript特有,其實在.NET1.0的時候,我們就可以用委托來實現回調了。但是今天我們要實現的回調可就不一樣了。

void CreateTextBox()
{
	var tb = new TextBox();
	tb.IsReadOnly = true;
	tb.Text = "Please wait ...";
	DoSomeStuff(() => {
		tb.Text = string.Empty;
		tb.IsReadOnly = false;
	});
}

void DoSomeStuff(Action callback)
{
	// Do some stuff - asynchronous would be helpful ...
	callback();
}

  上面的代碼中,我們在DoSomeStuff完成之後,再做一些事情。這種寫法在JavaScript中是很常見的,jQuery中的Ajax的oncompleted, onsuccess不就是這樣實現的麽?又或者LINQ擴展方法中的foreach不也是這樣的麽?

返回方法

  我們在JavaScript中可以直接return一個方法,在.net中雖然不能直接返回方法,但是我們可以返回一個表達式。

Func<string, string> SayMyName(string language)
{
	switch(language.ToLower())
	{
		case "fr":
			return name => {
				return "Je m'appelle " + name + ".";
			};
		case "de":
			return name => {
				return "Mein Name ist " + name + ".";
			};
		default:
			return name => {
				return "My name is " + name + ".";
			};
	}
}

void Main()
{
	var lang = "de";
	//Get language - e.g. by current OS settings
	var smn = SayMyName(lang);
	var name = Console.ReadLine();
	var sentence = smn(name);
	Console.WriteLine(sentence);
}

  是不是有一種策略模式的感覺?這還不夠完美,這一堆的switch case看著就心煩,讓我們用Dictionary<TKey,TValue>來簡化它。來看看來面這貨:

static class Translations
{
	static readonly Dictionary<string, Func<string, string>> smnFunctions = new Dictionary<string, Func<string, string>>();

	static Translations()
	{
		smnFunctions.Add("fr", name => "Je m'appelle " + name + ".");
		smnFunctions.Add("de", name => "Mein Name ist " + name + ".");
		smnFunctions.Add("en", name => "My name is " + name + ".");
	}

	public static Func<string, string> GetSayMyName(string language)
	{
		//Check if the language is available has been omitted on purpose
		return smnFunctions[language];
	}
}

自定義型方法

  自定義型方法在JavaScript中比較常見,主要實現思路是這個方法被設置成一個屬性。在給這個屬性附值,甚至執行過程中我們可以隨時更改這個屬性的指向,從而達到改變這個方法的目地。

class SomeClass
{
	public Func<int> NextPrime
	{
		get;
		private set;
	}

	int prime;

	public SomeClass
	{
		NextPrime = () => {
			prime = 2;

			NextPrime = () => {
                   // 這裡可以加上 第二次和第二次以後執行NextPrive()的邏輯代碼 return prime; }; return prime; } } }

  上面的代碼中當NextPrime第一次被調用的時候是2,與此同時,我們更改了NextPrime,我們可以把它指向另外的方法,和JavaScrtip的靈活性比起來也不差吧?如果你還不滿意 ,那下麵的代碼應該能滿足你。

Action<int> loopBody = i => {
	if(i == 1000)
		loopBody = //把loopBody指向別的方法

	/* 前10000次執行下麵的代碼 */
};

for(int j = 0; j < 10000000; j++)
	loopBody(j);

  在調用的地方我們不用考慮太多,然後這個方法本身就具有調優性了。我們原來的做法可能是在判斷i==1000之後直接寫上相應的代碼,那麼和現在的把該方法指向另外一個方法有什麼區別呢?

自執行方法

  JavaScript 中的自執行方法有以下幾個優勢:

  1. 不會污染全局環境
  2. 保證自執行裡面的方法只會被執行一次
  3. 解釋完立即執行

  在C#中我們也可以有自執行的方法:

(() => {
	// Do Something here!
})();

  上面的是沒有參數的,如果你想要加入參數,也非常的簡單:

((string s, int no) => {
	// Do Something here!
})("Example", 8);

  .NET4.5最閃的新功能是什麼?async?這裡也可以

await (async (string s, int no) => {
	// 用Task非同步執行這裡的代碼
})("Example", 8);

// 非同步Task執行完之後的代碼  

對象即時初始化

  大家知道.NET為我們提供了匿名對象,這使用我們可以像在JavaScript裡面一樣隨意的創建我們想要對象。但是別忘了,JavaScript裡面可以不僅可以放入數據,還可以放入方法,.NET可以麽?要相信,Microsoft不會讓我們失望的。

//Create anonymous object
var person = new {
	Name = "Jesse",
	Age = 28,
	Ask = (string question) => {
		Console.WriteLine("The answer to `" + question + "` is certainly 42!");
	}
};

//Execute function
person.Ask("Why are you doing this?");

  但是如果你真的是運行這段代碼,是會拋出異常的。問題就在這裡,Lambda表達式是不允許賦值給匿名對象的。但是委托可以,所以在這裡我們只需要告訴編譯器,我是一個什麼類型的委托即可。

var person = new {
	Name = "Florian",
	Age = 28,
	Ask = (Action<string>)((string question) => {
		Console.WriteLine("The answer to `" + question + "` is certainly 42!");
	})
};

  但是這裡還有一個問題,如果我想在Ask方法裡面去訪問person的某一個屬性,可以麽?

var person = new
{
                Name = "Jesse",
                Age = 18,
                Ask = ((Action<string>)((string question) => {
                    Console.WriteLine("The answer to '" + question + "' is certainly 20. My age is " + person.Age );
                }))
};

  結果是連編譯都通不過,因為person在我們的Lambda表達式這裡還是沒有定義的,當然不允許使用了,但是在JavaScript裡面是沒有問題的,怎麼辦呢?.NET能行麽?當然行,既然它要提前定義,我們就提前定義好了。

dynamic person = null;
person = new {
	Name = "Jesse",
	Age = 28,
	Ask = (Action<string>)((string question) => {
		Console.WriteLine("The answer to `" + question + "` is certainly 42! My age is " + person.Age + ".");
	})
};

//Execute function
person.Ask("Why are you doing this?");  

運行時分支

  這個模式和自定義型方法有點類似,唯一的不同是它不是在定義自己,而是在定義別的方法。當然,只有當這個方法基於屬性定義的時候才有這種實現的可能。

public Action AutoSave { get; private set; }

public void ReadSettings(Settings settings)
{
	/* Read some settings of the user */

	if(settings.EnableAutoSave)
		AutoSave = () => { /* Perform Auto Save */ };
	else
		AutoSave = () => { }; //Just do nothing!
}

  可能有人會覺得這個沒什麼,但是仔細想想,你在外面只需要調用AutoSave就可以了,其它的都不用管。而這個AutoSave,也不用每次執行的時候都需要去檢查配置文件了。

總結

  Lambda表達式在最後編譯之後實質是一個方法,而我們聲明Lambda表達式呢實質上是以委托的形式傳遞的。當然我們還可以通過泛型表達式Expression來傳遞。通過Lambda表達式形成閉包,可以做很多事情,但是有一些用法現在還存在爭議,本文只是做一個概述 :),如果有不妥,還請拍磚。謝謝支持 :)

還有更多Lambda表達式的新鮮玩法,請移步: 背後的故事之 - 快樂的Lambda表達式(二)

 原文鏈接: http://www.codeproject.com/Articles/507985/Way-to-Lambda


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 第一種: string[] myArray = new string[10]; for(int i = 0; i<10;i++){ myArray[i] = i+1; } 第二種: string[] myArray2 = {"1","2","3","4","5","6","7","8","9","1 ...
  • 1.什麼是RESTful? REST,即Representational State Transfer的縮寫。"(資源的)表現層狀態轉化"。 2.什麼是表現層? "資源"具體呈現出來的形式,叫做它的"表現層"(Representation)。比如,文本可以用txt格式表現,也可以用HTML格式、XM ...
  • 創建響應式WinForm應用程式並不那麼簡單。 響應式佈局,我們在此指的是在不同屏幕解析度下的可用性。 對於WinForm應用程式,我們需要明確地根據解析度來調整控制項的大小和重新定位。 雖然在使用WPF時有相關的實踐應用,通過使用控制項的docking和anchoring,或使用panels等方法,但... ...
  • BlockingCollection集合是一個擁有阻塞功能的集合,它就是完成了經典生產者消費者的演算法功能。一般情況下,我們可以基於 生產者 - 消費者模式來實現併發。BlockingCollection<T> 類是最好的解決方案 剛結束的物聯網卡項目,我需要調用移動的某個具有批量獲取物聯網卡數據的接 ...
  • 概述 在之前寫的一篇關於async和await的前世今生的文章之後,大家似乎在async和await提高網站處理能力方面還有一些疑問,博客園本身也做了不少的嘗試。今天我們再來回答一下這個問題,同時我們會做一個async和await在WinForm中的嘗試,並且對比在4.5之前的非同步編程模式APM/E ...
  • 一個網頁,它是顯示圖片,但在一些瀏覽器,它卻顯示如下: Insus.NET猜,不是瀏覽器不相容,就是代碼有問題。 在代碼中,只是輸出數據流,圖片格式很多種,如jpg,png,bmp等,沒有指定,程式也不清楚要顯示什麼格式的圖片。因此,Insus.NET把代碼改為如下: context.Respons ...
  • 為什麼要學習表達式樹?表達式樹是將我們原來可以直接由代碼編寫的邏輯以表達式的方式存儲在樹狀的結構里,從而可以在運行時去解析這個樹,然後執行,實現動態的編輯和執行代碼。LINQ to SQL就是通過把表達式樹翻譯成SQL來實現的,所以瞭解表達樹有助於我們更好的理解 LINQ to SQL,同時如果你有 ...
  • 快樂的Lambda表達式 上一篇 背後的故事之 - 快樂的Lambda表達式(一)我們由淺入深的分析了一下Lambda表達式。知道了它和委托以及普通方法的區別,並且通過測試對比他們之間的性能,然後我們通過IL代碼深入瞭解了Lambda表達式,以及介紹瞭如何在.NET中用Lambda表達式來實現Jav ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...