日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > C# >内容正文

C#

译 | 你到底有多精通 C# ?

發(fā)布時(shí)間:2023/12/4 C# 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 译 | 你到底有多精通 C# ? 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

點(diǎn)擊上方藍(lán)字關(guān)注“汪宇杰博客”

文:Damir Arh

譯:Edi Wang

即使是具有良好 C# 技能的開(kāi)發(fā)人員有時(shí)候也會(huì)編寫可能會(huì)出現(xiàn)意外行為的代碼。本文介紹了屬于該類別的幾個(gè) C# 代碼片段,并解釋了令人驚訝的行為背后的原因。

Null 值


我們都知道,如果處理不當(dāng),空值(null)可能是危險(xiǎn)的。

使用一個(gè)空值對(duì)象(例如,在一個(gè)null對(duì)象上調(diào)用方法,或訪問(wèn)它的一個(gè)屬性)會(huì)導(dǎo)致?NullReferenceException ,例如:

object nullValue = null;

bool areNullValuesEqual = nullValue.Equals(null);

為了安全起見(jiàn),我們?cè)谑褂靡妙愋椭靶枰_保它們不為 null 。如果不這樣做,可能會(huì)導(dǎo)致特定邊緣情況下的未處理異常。雖然這樣的錯(cuò)誤偶爾會(huì)發(fā)生在每個(gè)人身上,但我們幾乎不能稱之為意外行為。

但是,下面的代碼呢?

string nullString = (string)null;

bool isStringType = nullString is string;

isStringType 的值是什么?顯式申明為字符串的變量是否也會(huì)在運(yùn)行時(shí)作為字符串類型?

正確的答案是:

null 值在運(yùn)行時(shí)是沒(méi)有類型的

從某種程度上說(shuō),這也會(huì)影響反射。當(dāng)然,您不能在空值上調(diào)用 GetType(),因?yàn)闀?huì)引發(fā)空引用異常:

object nullValue = null;

Type nullType = nullValue.GetType();

接下來(lái),我們看看可空的值類型

int intValue = 5;

Nullable<int> nullableIntValue = 5;

bool areTypesEqual = intValue.GetType() == nullableIntValue.GetType();

是否可以使用反射來(lái)區(qū)分可空值類型和不可空值類型?

答案是:不可以

上述代碼中的兩個(gè)變量返回相同的類型: System.Int32。不過(guò),這并不意味著反射對(duì) Nullable<T> 沒(méi)有表示。

Type intType = typeof(int);

Type nullableIntType = typeof(Nullable<int>);

bool areTypesEqual = intType == nullableIntType;

此代碼段中的類型是不同的。如預(yù)期的那樣,可空類型將用 System.Nullable'1[[System.Int32] 表示。只有在檢查值時(shí),才會(huì)將值視為反射中的不可空值。

重載方法中的 null 值

在轉(zhuǎn)到其他話題之前,讓我們仔細(xì)了解在調(diào)用參數(shù)數(shù)量相同但類型不同的重載方法時(shí)如何處理空值。

private string OverloadedMethod(object arg)

{

????return "object parameter";

}

?

private string OverloadedMethod(string arg)

{

????return "string parameter ";

}

如果我們使用空(null)值調(diào)用這個(gè)方法,會(huì)發(fā)生什么情況?

var result = OverloadedMethod(null);

將調(diào)用哪個(gè)重載?還是代碼會(huì)因?yàn)榉椒ㄕ{(diào)用不明確而無(wú)法編譯?

在這種情況下,代碼可以編譯,并調(diào)用具有字符串參數(shù)的方法。

通常,當(dāng)一個(gè)參數(shù)類型可以轉(zhuǎn)換成一個(gè)參數(shù)類型 (即一個(gè)參數(shù)類型從另一個(gè)參數(shù)類型派生) 時(shí),代碼可以編譯。將調(diào)用具有更具體參數(shù)類型的方法。

當(dāng)這兩種類型之間不可以轉(zhuǎn)換時(shí),代碼將不會(huì)編譯。

若要強(qiáng)制調(diào)用特定重載, 可以將空值強(qiáng)制轉(zhuǎn)換為該參數(shù)類型:

var result = parameteredMethod((object)null);

算術(shù)運(yùn)算

我們大多數(shù)人并不經(jīng)常使用位移位操作。

讓我們先刷新一下記憶。左移運(yùn)算符 (<<) 將二進(jìn)制表示向左移動(dòng)給定數(shù)量的位置:

var shifted = 0b1 << 1; // = 0b10

同樣, 右移位運(yùn)算符 (>>) 將二進(jìn)制表示形式向右移動(dòng):

var shifted = 0b1 >> 1; // = 0b0

當(dāng)這些位(bit)到達(dá)終點(diǎn)時(shí),它們不會(huì)換行(wrap)。這就是為什么第二個(gè)表達(dá)式的結(jié)果是0。如果我們將位移動(dòng)到足夠遠(yuǎn)的左側(cè) (32位, 因?yàn)檎麛?shù)是32位數(shù)字),也會(huì)發(fā)生同樣的情況:

var shifted = 0b1;

for (int i = 0; i < 32; i++)

{

????shifted = shifted << 1;

}

結(jié)果將再次為0。

但是, 位移位運(yùn)算符具有第二個(gè)操作數(shù)。我們可以向左移動(dòng) 32位,而不是向左移動(dòng)1位32次,并獲得相同的結(jié)果。

var shifted = 0b1 << 32;

是這樣嗎?這是錯(cuò)的!

此表達(dá)式的結(jié)果將是1。為什么?

因?yàn)檫@就是運(yùn)算符的定義方式。在應(yīng)用操作之前,第二個(gè)操作數(shù)將使用模數(shù)操作將被歸一操作的位長(zhǎng)度規(guī)范化,即通過(guò)計(jì)算第二個(gè)操作數(shù)除以第一個(gè)操作數(shù)的位長(zhǎng)度的剩余部分。

我們剛才看到的示例中的第一個(gè)操作數(shù)是32位數(shù)字,因此:32 % 32 = 0。我們的數(shù)字將向左移動(dòng)0位。這和把它移1位32次是不一樣的。

讓我們繼續(xù)操作 & (和) | (或)。根據(jù)操作數(shù)的類型,它們表示兩種不同的操作:

  • 對(duì)于布爾操作數(shù),它們充當(dāng)邏輯運(yùn)算符,類似于 && 和 ||,有一個(gè)區(qū)別:它們是饑餓的(eager),即始終計(jì)算兩個(gè)操作數(shù),即使在評(píng)估第一個(gè)操作數(shù)后就可以確定結(jié)果。

  • 對(duì)于整數(shù)類型,它們充當(dāng)邏輯按位運(yùn)算符,通常用于表示 Flag 的枚舉類型。

[Flags]
private enum Colors
{
? ?None = 0b0,
? ?Red = 0b1,
? ?Green = 0b10,
? ?Blue = 0b100
}

| 運(yùn)算符用于組合標(biāo)志(Flag),& 運(yùn)算符用于檢查是否設(shè)置了標(biāo)志:

Colors color = Colors.Red | Colors.Green;
bool isRed = (color & Colors.Red) == Colors.Red;

在上面的代碼中,我在按位邏輯操作前后加上括號(hào),以使代碼更加清晰。此表達(dá)式中是否需要括號(hào)?

事實(shí)證明,是的

與算術(shù)運(yùn)算符不同,按位邏輯運(yùn)算符的優(yōu)先級(jí)低于相等運(yùn)算符。幸運(yùn)的是,由于類型檢查,沒(méi)有括號(hào)的代碼將無(wú)法編譯。

從 .NET Framework 4.0 起,有一個(gè)更好的替代方法可用于檢查標(biāo)志,您應(yīng)該始終使用它,而不是 & 運(yùn)算符:

bool isRed = color.HasFlag(Colors.Red);

Math.Round()

我們以Round為例繼續(xù)聊算術(shù)運(yùn)算操作。它如何在兩個(gè)整數(shù)值 (例如 1.5) 之間的中點(diǎn)舍入值?向上還是向下?

var rounded = Math.Round(1.5);

如果你預(yù)測(cè)是2,你是對(duì)的。結(jié)果將是2。這是一般規(guī)則嗎?

var rounded = Math.Round(2.5);

不。結(jié)果將再次為2。默認(rèn)情況下,中點(diǎn)值將Round到最接近的偶數(shù)值。您可以為方法提供第二個(gè)參數(shù),以顯式請(qǐng)求此類行為:

var rounded = Math.Round(2.5, MidpointRounding.ToEven);

可以使用第二個(gè)參數(shù)的不同值更改行為:

var rounded = Math.Round(2.5, MidpointRounding.AwayFromZero);

有了這個(gè)明確的規(guī)則,正值現(xiàn)在總是向上舍入。

舍入數(shù)字也會(huì)受到浮點(diǎn)數(shù)精度的影響。

var value = 1.4f;
var rounded = Math.Round(value + 0.1f);

雖然中點(diǎn)值應(yīng)舍入到最接近的偶數(shù),即 2,但在這種情況下,結(jié)果將是 1,因?yàn)閷?duì)于單精度浮點(diǎn)數(shù),0.1 沒(méi)有精確的表示形式,計(jì)算的數(shù)字實(shí)際上將小于 1.5 并因此Round到1。

盡管在使用雙精度浮點(diǎn)數(shù)時(shí)沒(méi)有出現(xiàn)此特定問(wèn)題,但舍入錯(cuò)誤仍可能發(fā)生,盡管頻率較低。因此,在要求最大精度時(shí),應(yīng)始終使用小數(shù)而不是浮動(dòng)或雙精度。

類初始化

最佳實(shí)踐建議盡可能避免類構(gòu)造函數(shù)中的類初始化,以防止異常。

所有這些對(duì)于靜態(tài)構(gòu)造函數(shù)來(lái)說(shuō)都更加重要。

您可能知道,當(dāng)我們嘗試在運(yùn)行時(shí)實(shí)例化靜態(tài)構(gòu)造函數(shù)時(shí),它在實(shí)例構(gòu)造函數(shù)之前調(diào)用。

這是實(shí)例化任何類時(shí)的初始化順序:

  • 靜態(tài)字段 (僅限第一次類訪問(wèn): 靜態(tài)成員或第一個(gè)實(shí)例)

  • 靜態(tài)構(gòu)造函數(shù) (僅限第一次類訪問(wèn): 靜態(tài)成員或第一個(gè)實(shí)例)

  • 實(shí)例字段 (每個(gè)實(shí)例)

  • 實(shí)例構(gòu)造函數(shù) (每個(gè)實(shí)例)

讓我們創(chuàng)建一個(gè)具有靜態(tài)構(gòu)造函數(shù)的類,可以將其配置為引發(fā)異常:

public static class Config
{
? ?public static bool ThrowException { get; set; } = true;
}

public class FailingClass
{
? ?static FailingClass()
? ?{
? ? ? ?if (Config.ThrowException)
? ? ? ?{
? ? ? ? ? ?throw new InvalidOperationException();
? ? ? ?}
? ?}
}

創(chuàng)建此類實(shí)例的任何嘗試都會(huì)導(dǎo)致異常,這不應(yīng)該讓人感到意外:

var instance = new FailingClass();

但是,它不會(huì)是?InvalidOperationException?。運(yùn)行時(shí)將自動(dòng)將其包裝到?TypeInitializationException?中。如果要捕獲異常并從中恢復(fù),這是需要注意的重要詳細(xì)信息。

try
{
? ?var failedInstance = new FailingClass();
}
catch (TypeInitializationException) { }
Config.ThrowException = false;
var instance = new FailingClass();

應(yīng)用我們所學(xué)到的知識(shí),上面的代碼應(yīng)該捕獲靜態(tài)構(gòu)造函數(shù)引發(fā)的異常,更改配置以避免在以后的調(diào)用中引發(fā)異常,最后成功地創(chuàng)建類的實(shí)例,對(duì)嗎?

不幸的是,不對(duì)。

類的靜態(tài)構(gòu)造函數(shù)只調(diào)用一次。如果它引發(fā)異常,則每當(dāng)您要?jiǎng)?chuàng)建實(shí)例或以任何其他方式訪問(wèn)類時(shí),都將重新引發(fā)此異常。

在重新啟動(dòng)進(jìn)程 (或應(yīng)用程序域) 之前,該類實(shí)際上無(wú)法使用。是的,即使靜態(tài)構(gòu)造函數(shù)引發(fā)異常的可能性很小,也是一個(gè)非常糟糕的想法。

派生類中的初始化順序

對(duì)于派生類,初始化順序更加復(fù)雜。在邊緣情況下,這可能會(huì)給你帶來(lái)麻煩。是時(shí)候做一個(gè)人為的例子了:

public class BaseClass
{
? ?public BaseClass()
? ?{
? ? ? ?VirtualMethod(1);
? ?}

? ?public virtual int VirtualMethod(int dividend)
? ?{
? ? ? ?return dividend / 1;
? ?}
}

public class DerivedClass : BaseClass
{
? ?int divisor;
? ?public DerivedClass()
? ?{
? ? ? ?divisor = 1;
? ?}

? ?public override int VirtualMethod(int dividend)
? ?{
? ? ? ?return base.VirtualMethod(dividend / divisor);
? ?}
}

你能在衍生類中發(fā)現(xiàn)一個(gè)問(wèn)題嗎?當(dāng)我嘗試實(shí)例化它時(shí), 會(huì)發(fā)生什么?

var instance = new DerivedClass();

將引發(fā)一個(gè) DivideByZeroException?。為什么?

原因是派生類的初始化順序:

  • 首先,實(shí)例字段按從派生最遠(yuǎn)的到基類的順序進(jìn)行初始化。

  • 其次,構(gòu)造函數(shù)按從基類到派生最遠(yuǎn)的類的順序調(diào)用。

由于在整個(gè)初始化過(guò)程中,該類被視為 DerivedClass,我們?cè)?BaseClass 構(gòu)造函數(shù)中調(diào)用 VirtualMethod 這個(gè)方法的實(shí)現(xiàn)其實(shí)是 DerivedClass 里的實(shí)現(xiàn),這時(shí)候DerivedClass 的構(gòu)造函數(shù)還沒(méi)機(jī)會(huì)初始化 divisor 字段。這意味著該值仍然為 0,這導(dǎo)致了DivideByZeroException

在我們的示例中,可以通過(guò)直接初始化除數(shù)字段而不是在構(gòu)造函數(shù)中來(lái)解決此問(wèn)題。

然而,該示例說(shuō)明了為什么從構(gòu)造函數(shù)調(diào)用虛擬方法可能很危險(xiǎn)。當(dāng)調(diào)用它們時(shí),它們?cè)谥卸x的類的構(gòu)造函數(shù)可能尚未調(diào)用,因此它們可能會(huì)出現(xiàn)意外行為。

多態(tài)性

多態(tài)性是不同類以不同的方式實(shí)現(xiàn)相同接口的能力。

不過(guò),我們通常期望單個(gè)實(shí)例始終使用相同的方法實(shí)現(xiàn),無(wú)論它是由哪個(gè)類型強(qiáng)制轉(zhuǎn)換的。這樣就可以將集合作為基類,并在集合中的所有實(shí)例上調(diào)用特定方法,從而為要調(diào)用的每個(gè)類型實(shí)現(xiàn)特定的方法。

話雖如此,但當(dāng)我們?cè)谡{(diào)用該方法之前向下轉(zhuǎn)換實(shí)例時(shí),你能想出一種方法來(lái)調(diào)用不同的方法嗎?(即打破多態(tài)行為)

var instance = new DerivedClass();
var result = instance.Method(); // -> Method in DerivedClass
result = ((BaseClass)instance).Method(); // -> Method in BaseClass

正確的答案是: 通過(guò)使用 new 修飾符。

public class BaseClass

{

????public virtual string Method()

????{

????????return "Method in BaseClass ";

????}

}

?

public class DerivedClass : BaseClass

{

????public new string Method()

????{

????????return "Method in DerivedClass";

????}

}

這將從其基類中隱藏 DerivedClass.Method,因此在將實(shí)例轉(zhuǎn)換為基類時(shí)調(diào)用 BaseClass.Method

這適用于基類,基類可以有自己的方法實(shí)現(xiàn)。對(duì)于不能包含自己的方法實(shí)現(xiàn)的接口,你能想出一個(gè)實(shí)現(xiàn)相同目標(biāo)的方法嗎?

var instance = new DerivedClass();

var result = instance.Method(); // -> Method in DerivedClass

result = ((IInterface)instance).Method(); // -> Method belonging to IInterface

它是顯式接口實(shí)現(xiàn)

public interface IInterface

{

????string Method();

}


public class DerivedClass : IInterface

{

????public string Method()

????{

????????return "Method in DerivedClass";

????}

?

????string IInterface.Method()

????{

????????return "Method belonging to IInterface";

????}

}

它通常用于向?qū)崿F(xiàn)它的類的使用者隱藏接口方法,除非他們將實(shí)例轉(zhuǎn)換到該接口。但是,如果我們希望在單個(gè)類中具有兩個(gè)不同的方法實(shí)現(xiàn),它的效果也一樣好。不過(guò),很難想出做這件事的好理由。

迭代器

迭代器是用于單步執(zhí)行構(gòu)造集合的結(jié)構(gòu),通常使用 foreach 語(yǔ)句。它們由 IEnumerable<T> 類型表示。

雖然它們很容易使用,但由于一些編譯器的魔力,如果我們不能很好地理解內(nèi)部工作原理,我們很快就會(huì)陷入不正確用法的陷阱。

讓我們看一下這樣的例子。我們將調(diào)用一個(gè)方法,該方法從 using 內(nèi)部返回一個(gè) IEnumerable:

private IEnumerable<int> GetEnumerable(StringBuilder log)

{

????using (var context = new Context(log))

????{

????????return Enumerable.Range(1, 5);

????}

}

當(dāng)然,Context 類型實(shí)現(xiàn)了?IDisposable。它將向日志寫入一條消息, 以指示何時(shí)輸入和退出其作用域。在實(shí)際代碼中, 此上下文可以被數(shù)據(jù)庫(kù)連接所取代。在它里面, 將以流式的方式從返回的結(jié)果集中讀取行。

public class Context : IDisposable

{

????private readonly StringBuilder log;

?

????public Context(StringBuilder log)

????{

????????this.log = log;

????????this.log.AppendLine("Context created");

????}

?

????public void Dispose()

????{

????????this.log.AppendLine("Context disposed");

????}

}

若要使用 GetEnumerable 返回值, 我們使用 foreach 循環(huán):

var log = new StringBuilder();

foreach (var number in GetEnumerable(log))

{

????log.AppendLine($"{number}");

}

代碼執(zhí)行后,日志的內(nèi)容將是什么?返回的值是否會(huì)在上下文創(chuàng)建和處置之間列出?

不,他們不會(huì):

Context created

Context disposed

1

2

3

4

5

這意味著,在我們的實(shí)際數(shù)據(jù)庫(kù)示例中,代碼將失敗--在從數(shù)據(jù)庫(kù)中讀取值之前,連接將被關(guān)閉。

我們?nèi)绾涡迯?fù)代碼,以便只有在所有值都已迭代后才會(huì)釋放上下文?

執(zhí)行此操作的唯一方法是循環(huán)訪問(wèn)已在 GetEnumerable 方法中的集合:

private IEnumerable<int> GetEnumerable(StringBuilder log)

{

????using (var context = new Context(log))

????{

????????foreach (var i in Enumerable.Range(1, 5))

????????{

????????????yield return i;

????????}

????}

}

當(dāng)我們現(xiàn)在循環(huán)訪問(wèn)返回的 IEnumerable 時(shí),上下文將只按預(yù)期的方式在末尾進(jìn)行釋放:

Context created

1

2

3

4

5

Context disposed

如果您不熟悉 yield return 語(yǔ)句,它是用于創(chuàng)建狀態(tài)機(jī)的語(yǔ)法糖,允許以增量方式執(zhí)行使用它的方法中的代碼,因?yàn)樯傻?IEnumerable 正在被迭代。

這可以用下面的方法更好地解釋:

private IEnumerable<int> GetCustomEnumerable(StringBuilder log)

{

????log.AppendLine("before 1");

????yield return 1;

????log.AppendLine("before 2");

????yield return 2;

????log.AppendLine("before 3");

????yield return 3;

????log.AppendLine("before 4");

????yield return 4;

????log.AppendLine("before 5");

????yield return 5;

????log.AppendLine("before end");

}

若要查看這段代碼的行為,我們可以使用以下代碼對(duì)其進(jìn)行循環(huán)訪問(wèn):

var log = new StringBuilder();

log.AppendLine("before enumeration");

foreach (var number in GetCustomEnumerable(log))

{

????log.AppendLine($"{number}");

}

log.AppendLine("after enumeration");

讓我們看看代碼執(zhí)行后的日志內(nèi)容:

before enumeration

before 1

1

before 2

2

before 3

3

before 4

4

before 5

5

before end

after enumeration

我們可以看到, 對(duì)于我們遍歷的每個(gè)值,兩個(gè) yield return 語(yǔ)句之間的代碼都會(huì)被執(zhí)行。

對(duì)于第一個(gè)值,這是從方法開(kāi)始到第一個(gè) yield return 語(yǔ)句的代碼。對(duì)于第二個(gè)值,它是第一個(gè)和第二個(gè) yield return 語(yǔ)句之間的代碼。以此類推,直到方法結(jié)束。

當(dāng) foreach 循環(huán)在循環(huán)的最后一次迭代之后檢查 IEnumerable 中的下一個(gè)值時(shí),將調(diào)用最后一個(gè) yield return 語(yǔ)句之后的代碼。

同樣值得注意的是,每次我們通過(guò) IEnumerable 迭代時(shí),都會(huì)執(zhí)行此代碼:

var log = new StringBuilder();

var enumerable = GetCustomEnumerable(log);

for (int i = 1; i <= 2; i++)

{

????log.AppendLine($"enumeration #{i}");

????foreach (var number in enumerable)

????{

????????log.AppendLine($"{number}");

????}

}

執(zhí)行此代碼后,日志將具有以下內(nèi)容:

enumeration #1

before 1

1

before 2

2

before 3

3

before 4

4

before 5

5

before end

enumeration #2

before 1

1

before 2

2

before 3

3

before 4

4

before 5

5

before end

為了防止每次我們通過(guò) IEnumerable 迭代時(shí)執(zhí)行代碼,最好將 IEnumerable 的結(jié)果存儲(chǔ)到本地集合 (例如, list) 中,如果我們計(jì)劃多次使用它,則從那里讀取它:

var log = new StringBuilder();

var enumerable = GetCustomEnumerable(log).ToList();

for (int i = 1; i <= 2; i++)

{

????log.AppendLine($"enumeration #{i}");

????foreach (var number in enumerable)

????{

????????log.AppendLine($"{number}");

????}

}

現(xiàn)在,代碼將只執(zhí)行一次--在我們創(chuàng)建列表時(shí),然后再對(duì)其進(jìn)行迭代:

before 1

before 2

before 3

before 4

before 5

before end

enumeration #1

1

2

3

4

5

enumeration #2

1

2

3

4

5

當(dāng)我們正在迭代的 IEnumerable 后面有緩慢的 I/O 操作時(shí),這一點(diǎn)尤其重要。數(shù)據(jù)庫(kù)訪問(wèn)也是一個(gè)典型的例子。

結(jié)論

您是否正確地預(yù)測(cè)了文章中所有示例的行為?

如果沒(méi)有,您可能已經(jīng)了解到,當(dāng)您不能完全確定特定功能是如何實(shí)現(xiàn)的時(shí),采取行為可能是危險(xiǎn)的。不可能知道并記住一種語(yǔ)言中的每一個(gè)邊緣案例,因此,當(dāng)您對(duì)遇到的一段重要代碼不確定時(shí),最好檢查文檔或自己先嘗試一下。

更重要的是,這其中的任何一項(xiàng)都是為了避免編寫可能會(huì)讓其他開(kāi)發(fā)人員感到驚訝的代碼 (或者在經(jīng)過(guò)一定時(shí)間后甚至可能是您)。嘗試以不同的方式編寫它或傳遞該可選參數(shù)的默認(rèn)值 (如我們的 Math.Round 中的示例),以使意圖更清晰。

如果這行不通,就寫測(cè)試方法。他們將清楚地記錄預(yù)期的行為!

你能正確地預(yù)測(cè)哪些?在評(píng)論中讓我們知道吧。

Yacoub Masd 對(duì)該文章進(jìn)行了技術(shù)審查。

Suprotim Agarwal 對(duì)本文進(jìn)行了編輯審查。


總結(jié)

以上是生活随笔為你收集整理的译 | 你到底有多精通 C# ?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。