C#最受歡迎功能 -- C#1至C#7

不定時更新翻譯系列,此係列更新毫無時間規律,文筆菜翻譯菜求各位看官老爺們輕噴,如以爲我翻譯有問題請挪步原博客地址javascript

本博文翻譯自:
http://www.dotnetcurry.com/csharp/1411/csharp-favorite-featureshtml

在這篇文章中,請您和我一塊兒瀏覽C#的各類版本,並分享每一個版本中我最喜歡的特性。我將在強調實用性的同時展現其優勢。java

C#我最喜歡的功能 - V1至V7

C#1.0版本

C#1.0版本(ISO-1)真的是一種很是無趣的東西,沒有什麼特別使人興奮的東西,並且它缺乏不少開發者喜歡的語言。然而,有一種特別的特徵,我認爲是我最喜歡的。- 隱式和顯式接口實現。數據庫

接口一直在使用,而且在現代的C#中仍然很流行。如下面的IDateProvider接口爲例。編程

public interface IDateProvider
{
    DateTime GetDate();
}

沒有什麼特別的,如今設想兩個實現 - 其中第一個隱式實現以下:swift

public class DefaultDateProvider : IDateProvider
{
    public DateTime GetDate() {
        return DateTime.Now;
    }
}

第二個顯示實現是這樣的:c#

public class MinDateProvider : IDateProvider {
    DateTime IDateProvider.GetDate()
    {
        return DateTime.MinValue;
    }
}

注意顯式實現如何省略訪問修飾符。此外,方法名稱被寫爲IDateProvider.GetDate(),,它將接口名稱做爲限定符的前綴。api

上面兩個例子使實現更加明確安全

顯式接口實現的一個簡潔之處是,它強制用戶依賴於接口。顯式實現接口的類的實例對象沒有可用的接口成員 - 而是必須使用接口自己。markdown

hidden-interface-members

可是,當您將其聲明爲接口或將此實現做爲預期接口的參數傳遞時,成員將按預期可用。

interface-members

當它強制使用接口時,這一點特別有用。經過直接使用接口,您不會將代碼耦合到底層實現。一樣,顯式接口實現處理命名或方法簽名的模糊性 - 並使單個類能夠實現具備相同成員的多個接口。

Jeffery Richter在他的書 CLR via C#中警告咱們關於顯式接口的現。兩個主要的關注點是,當轉換到顯式實現的接口和方法時,值類型被裝箱,而派生類型不能調用它們。

請記住,裝箱和拆箱會帶來額外能耗,和全部的編程同樣,您應該評估測試用例以肯定適合該工做的工具。

C#2.0版本

做爲參考,我將列出C#2.0(ISO-2)的全部功能。

  • 匿名方法
  • 協變和逆變
  • 泛型
  • 迭代器
  • 可空類型
  • 局部類型

我最喜歡的功能是介於泛型和迭代器之間,我選擇了泛型,下面我來講說緣由。

對我來講這是一個很是困難的選擇,我最終決定了泛型,由於我相信我比寫迭代器更頻繁地使用泛型。不少SOLID編程原則都是經過在C#中使用泛型來優化的,一樣它也有助於保持代碼的簡潔。不要誤解個人意思,我確實寫了不少迭代器,這是一個值得在你的C#中採用的特性!

讓咱們更詳細地看看泛型。

編者注: 學習如何使用 在C#中使用泛型來提升應用程序的可維護性。

泛型介紹: .NET Framework引入了類型參數的概念,這使得能夠設計類和方法來推遲一個或多個類型的規範,直到類或方法被客戶端代碼聲明和實例化爲止。

讓咱們設想一下,咱們有一個名爲DataBag的類,能夠做爲一個數據包。它可能看起來像這樣:

public class DataBag
{
    public void Add(object data) {
        // 爲了簡便起見,咱們省略了...
    }            
}

乍一看,這彷佛是個很棒的主意,由於您能夠在這個數據對象包的實例中添加任何東西。但當你真正思考這意味着什麼時,這多是至關使人擔心的。

全部添加的內容都隱式地轉到了System.Object。此外,若是添加了值類型,則會發生裝箱。這些是您應該注意的性能考慮事項。

泛型解決了這一切,同時也增長了類型安全性。讓咱們修改前面的例子,在類中包含一個類型參數T,並注意方法簽名的變化。

public class DataBag
{
    public void Add(T data) {
       // 爲了簡便起見,咱們省略了...
    }
}

如今,例如,DataBag實例只容許使用者添加DateTime實例。類型安全,沒有類型強制轉換或裝箱的世界是美好的。

泛型類型參數也能夠被限制。泛型約束是強大的,容許有限範圍的可用類型參數,由於它們必須遵照相應的約束。有幾種方法能夠編寫泛型類型參數約束,請參考如下語法:

public class DataBag where T : struct { /* T 是值類型*/ }
public class DataBag where T : class { /* T 能夠是類接口等引用類型*/ }
public class DataBag where T : new() { /* T 必須有無參構造函數 */ }
public class DataBag where T : IPerson { /* T 繼承IPerson */ }
public class DataBag where T : BaseClass { /* T 來源於BaseClass */ }
public class DataBag where T : U { /* T繼承U, U也是泛型類型參數。 */ }

多個約束是容許的,咱們只要用逗號分隔便可。類型參數約束當即被強制執行,這使得若是編譯錯誤能夠當即提醒咱們。讓咱們看看下面DataBag類的約束條件。

public class DataBag where T : class
{
    public void Add(T value) {
        // 爲了簡便起見,咱們省略了...
    }
}

如今,若是我試圖實例化DataBag,C#編譯器會讓我知道我作錯了什麼。更具體地說,它指出:

類型'DateTime'必須是一個引用類型,以便將其用做泛型類型或方法'Program.DataBag'中的參數'T'

C#3.0版本

這裏是C#3.0的主要功能列表。

  • 匿名類型
  • 自動實現屬性
  • 表達樹
  • 擴展方法
  • Lambda表達
  • 查詢表達式

我在選擇Lambda表達式的擴展方法的邊緣蹣跚而行。可是,當我思考今天寫的C#時,我實際上比其餘任何C#運算符都更多地使用lambda運算符

我喜歡寫富有表現力的C#。

在C#中有不少機會來利用lambda表達式和lambda運算符。使用=> lambda運算符將左邊的輸入與右邊的lambda體分開。

一些開發人員喜歡將lambda表達式看做是表達委託調用的一種較爲冗長的方式。Action,Func類型只是System命名空間中預先定義的泛型委託。

讓咱們從一個咱們試圖解決的問題開始,應用lambda表達式來幫助咱們編寫一些富有表現力和簡潔的C#代碼。

假設咱們有大量的記錄來表明天氣趨勢的信息。咱們可能但願對該數據執行一些不一樣的操做,而不是在一個典型的循環中遍歷它,由於咱們能夠以不一樣的方式處理這個問題。

public class WeatherData
{
    public DateTime TimeStampUtc { get; set; }
    public decimal Temperature { get; set; }
}

private IEnumerable GetWeatherByZipCode(string zipCode) { /* ... */ }

因爲GetWeatherByZipCode的方法調用返回了一個IEnumerable,看起來您可能想要在循環中迭代這個集合。假設咱們有一種計算平均溫度的方法,它能作這項工做。

private static decimal CalculateAverageTemperature( IEnumerable<WeatherData> weather, DateTime startUtc, DateTime endUtc) {
    var sumTemp = 0m;
    var total = 0;
    foreach (var weatherData in weather)
    {
        if (weatherData.TimeStampUtc > startUtc &&
            weatherData.TimeStampUtc < endUtc)
        {
            ++ total;
            sumTemp += weatherData.Temperature;
        }
    }
    return sumTemp / total;
}

咱們聲明一些局部變量來存儲在通過篩選的日期範圍內的全部溫度和它們的總和,而後計算平均值。在迭代中是一個邏輯if塊,其檢查天氣數據是否在特定日期範圍內。這能夠改寫以下:

private static decimal CalculateAverageTempatureLambda( IEnumerable<WeatherData> weather, DateTime startUtc, DateTime endUtc) {
    return weather.Where(w => w.TimeStampUtc > startUtc &&
                              w.TimeStampUtc  w.Temperature)
                  .Average();
}

如您所見,這大大簡化了。邏輯if塊實際上只是一個謂詞,若是天氣日期在範圍內,咱們將繼續進行一些額外的處理——好比過濾器。而後咱們把溫度相加,因此咱們只須要把這個項目選擇出來。咱們最終獲得了一個通過篩選的溫度列表,咱們如今能夠簡單地調用平均值。

lambda表達式被用做在通用IEnumerable接口上的Where和選擇擴展方法的參數。

C#4.0版本

從之前的版本發佈來看,C#4.0的主要特性數量較少。

  • 動態綁定
  • 嵌入式互操做類型
  • 泛型協變和逆變
  • 實名/可選參數

全部這些功能都是很是有用的。但對我來講,它歸結爲實名和可選參數,而不泛型協變和逆變。在這二者之間,我討論了我最常使用哪一個特性,而且在多年的時間裏,它確實使我受益最大。

我相信這個特性實名/可選的參數。這是一個很是簡單的功能,但實用性得分很高。個人意思是,誰沒有寫一個重載或可選參數的方法?

當您編寫可選參數時,您必須爲其提供一個默認值。若是你的參數是一個值類型,那麼它必須是一個字面值或者常數值,或者你可使用default關鍵字。一樣,您能夠將值類型聲明爲Nullable,並將其賦值爲null。讓咱們想象咱們有一個Repository類,並有一個GetData方法。

public class Repository
{
    public DataTable GetData( string storedProcedure, DateTime start = default(DateTime), DateTime? end = null,
        int? rows = 50,
        int? offSet = null)
    {
        //爲了簡便起見,咱們省略了... 
    }
}

咱們能夠看到,這個方法的參數列表至關長,可是有幾個任務。表示這些值是可選的。所以,調用者能夠省略它們,並使用默認值。正如您可能假設的那樣,咱們能夠僅經過提供存儲過程名稱來調用它。

var repo = new Repository();
var sales = repo.GetData("sp_GetHistoricalSales", rows: 100);

如今咱們已經熟悉了可選參數特性以及這些特性如何工做,讓咱們在這裏使用一些實名參數。以上面的示例爲例,假設咱們只但願咱們的數據表返回100行而不是默認的50行。咱們能夠將咱們的調用改成包含一個命名參數,並傳遞所需的重寫值。

var repo = new Repository();
var sales = repo.GetData("sp_GetHistoricalSales", rows: 100);

C#5.0版本

像C#4.0版本同樣,C#5.0版本中沒有太多功能 - 可是其中一個功能很是龐大。

  • 異步/等待
  • CallerInfoAttributes

當C#5.0發佈時,它實際上改變了C#開發人員編寫異步代碼的方式。雖然直到今天仍然有不少困惑,但我在這裏向您保證,這比大多數人想象的要簡單得多。這是C#的一個重大飛躍 - 它引入了一個語言級別的異步模型,它極大地賦予了開發人員編寫外觀和感受同步(或者至少是連續的)的「異步」代碼。

異步編程在處理I/O綁定工做負載(如與數據庫,網絡,文件系統等進行交互)時很是強大。異步編程經過使用非阻塞方法幫助處理吞吐量。這種方法使用了一個透明的異步狀態機中的掛點和相應的延續。

一樣,若是CPU負載計算的工做量很大,則可能須要考慮異步執行此項工做。這將有助於用戶體驗,由於UI線程不會被阻塞,而是能夠自由地響應其餘UI交互。

編者注:這裏有一些關於C#異步編程的最佳實踐,使用Async Await.

在C#5.0中,當語言添加了兩個新的關鍵字async和await時,異步編程被簡化了。這些關鍵字適用於Task。下表將做爲參考:

async-await

Task表示異步操做。操做能夠經過Task返回值,也能夠經過Task返回void。當您使用async關鍵字修飾Task返回方法時,它使方法主體可使用await關鍵字。當您請求await關鍵字的返回值時,控制流將返回給調用者,而且在方法的那個點執行暫停。當await的操做完成後,在同一點上恢復執行。部分代碼以下!

class IOBoundAsyncExample
{
  
    private const string Url = "http://api.icndb.com/jokes/random?limitTo=[nerdy]";
 
    internal async Task GetJokeAsync() {
        using (var client = new HttpClient())
        {
            var response = await client.GetStringAsync(Url);
            var result = JsonConvert.DeserializeObject(response);
 
            return result.Value.Joke;
        }
    }
}
public class Result
{
    [JsonProperty("type")] public string Type { get; set; }
    [JsonProperty("value")] public Value Value { get; set; }
}
 
public class Value
{
    [JsonProperty("id")] public int Id { get; set; }
    [JsonProperty("joke")] public string Joke { get; set; }
}

咱們用一個名爲GetJokeAsync的方法定義一個簡單的類。該方法是返回Task,這意味着咱們的GetJokeAsync方法最終會給您一個字符串,或者可能出錯。

該方法使用async關鍵字進行修飾,該關鍵字容許使用等待關鍵字。咱們實例化並使用一個HttpClient對象。而後咱們調用GetStringAsync函數,它接受一個字符串url並返回一個Task 。咱們等待從GetStringAsync調用返回的Task。

當響應已經準備好時,就會繼續發生並控制從咱們曾經掛起的位置恢復。而後,咱們將JSON反序列化到Result類的實例中,並返回Joke屬性。

一些我最喜歡的成果

  • 查克·諾里斯(Chuck Norris)能夠用單一的斷言來測試整個應用程序。
  • 查克·諾里斯(Chuck Norris)能夠編譯語法錯誤。
  • 項目經理永遠不會要求查克·諾里斯(Chuck Norris)作出估計。

歡鬧隨之而來!咱們瞭解了C#5.0的驚人的異步編程模型。

C#6.0版本

C#6.0的推出有不少很大的進步,很難選擇我最喜歡的功能。

  • 字典初始化
  • 異常過濾器
  • 在屬性裏使用Lambda表達式
  • nameof表達式
  • 空值運算符
  • 自動屬性初始化
  • 靜態導入
  • 字符串嵌入值

我把範圍縮小到三個突出特色:空值運算符,字符串嵌入值和nameof表達式。

雖然nameof表達式很棒,我幾乎每次都用它來編寫代碼,但其餘兩個特性更有影響力。這讓我在字符串嵌入值和空值運算符之間作出決定,這是至關困難的。我決定我最喜歡的是字符串嵌入值,這就是爲何。

空值運算符是偉大的,它容許我寫較少的詳細代碼,但它不必定能防止個人代碼中的錯誤。可是,使用字符串嵌入值能夠防止運行時錯誤 - 這是個人書中的一個勝利。

使用$符號啓動字符串文字時,將啓用C#中的字符串嵌入值語法。這指示C#編譯器打算用各類C#變量,邏輯或表達式來插入此字符串。這是手動字符串鏈接甚至是string.Format方法的一個主要升級。考慮如下:

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public override string ToString() => string.Format("{0} {1}", FirstName);
}

咱們有一個簡單的Person類,具備兩個名稱屬性,用於名字和姓氏。咱們重寫ToString方法並使用string.Format。問題是,編譯時,因爲開發人員顯然但願將姓氏也做爲結果字符串的一部分,所以很容易出錯,這一點在「{0} {1} 」參數中很明顯。一樣,開發人員能夠很容易地交換名稱或正確提供兩個名稱參數,但混亂的格式文字只包括第一個索引,等等...如今咱們能夠考慮使用字符串嵌入值。

class Person
{
    public string FirstName { get; set; } = "David";
    public string LastName { get; set; } = "Pine";
    public DateTime DateOfBirth { get; set; } = new DateTime(1984, 7, 7);

    public override string ToString() => $"{FirstName} {LastName} (Born {DateOfBirth:MMMM dd, yyyy})";
}

我冒昧添加DateOfBirth屬性和一些默認的屬性值。另外,咱們如今在咱們的ToString方法的覆蓋中使用字符串嵌入值。做爲一名開發人員,犯上述錯誤要困可貴多。最後,我也能夠在插值表達式中進行格式化。注意第三次嵌入值,DateOfBirth是一個DateTime - 所以咱們可使用您已經習慣的全部標準格式。只需使用:運算符來分隔變量和格式。

示例輸出

· David Pine (Born July 7, 1984)

編輯注:有關C#6.0新特性的詳細內容,請閱讀www.dotnetcurry.com/csharp/1042/csharp-6-new-features

C#7.0版本

從全部集成到 C# 7.0的特性中。

  • 更多的函數成員的表達式體
  • 局部函數
  • Out變量
  • 模式匹配
  • 局部變量和引用返回
  • 元組和解構

我結束了模式匹配,元組和Out變量之間的爭論。我最終選擇了Out變量,這是爲何。

模式匹配可是我真的不常用它,至少如今尚未。也許之後我會更多地使用它,可是對於我迄今爲止編寫的全部c#代碼,沒有太多地方能夠利用它。一樣,這是一個很棒的功能,我確實看到了它的位置 - 只是在C#7.0中這不是我最喜歡的。

元組也是一個很好的補充。元組是語言的重要組成部分,成爲一流的公民是很是棒的。我會說,「寫tem1,.Item2,.Item3等...的日子已通過去了,但這並不必定是正確的。反序列化失去了元組的名稱,使得這個公共API不那麼有價值

我也不喜歡ValueTuple類型是可變的這一事實。我只是不明白設計者的決定。我但願有人能給我解釋一下,但感受有點像疏忽。所以,我獲得了選擇out變量的特性。

自從C#版本1.0以來,try-parse模式已經在各類值類型中出現了。模式以下:

public boolean TryParse(string value, out DateTime date) {
    // 爲了簡便起見,咱們省略了.....
}

該函數返回一個布爾值,指示給定的字符串值是否可以被解析。若是爲true,則將分析的值分配給生成的輸出參數date。它的使用以下:

DateTime date;
if (DateTime.TryParse(someDateString, out date))
{
    //  date如今是解析值
}
else
{
    // date是DateTime.MinValue,默認值
}

這種模式是有用的,但有點麻煩。有時,無論解析是否成功,開發人員都會採起相同的操做過程。有時使用默認值是能夠的。C#7.0中的out變量使得這個更復雜,不過在我看來不那麼複雜。

示例以下:

if (DateTime.TryParse(someDateString, out var date))
{
    // date如今是解析值
}
else
{
    // date是DateTime.MinValue,默認值
}

如今咱們移除了if語句塊的外部聲明,並把聲明做爲參數自己的一部分。使用var是合法的,由於類型是已知的。最後,date變量的範圍沒有改變。它從內聯聲明泄漏到if塊的頂部。

你可能會問本身:「爲何這是他最喜歡的功能之一?」.....這種感受真的沒有什麼變化。

可是這改變了一切!

它使咱們的C#更具備表現力。每一個人都喜歡擴展方法,對 - 請考慮如下幾點:

public static class StringExtensions
{
    private delegate bool TryParseDelegate(string s, out T result);

    private static T To(string value, TryParseDelegate parse) => parse(value, out T result) ? result : default;

    public static int ToInt32(this string value) => To(value, int.TryParse);

    public static DateTime ToDateTime(this string value) => To(value, DateTime.TryParse);

    public static IPAddress ToIPAddress(this string value) => To(value, IPAddress.TryParse);

    public static TimeSpan ToTimeSpan(this string value) => To(value, TimeSpan.TryParse);
}

這個擴展方法類很簡潔,表達能力強。在定義了遵循try-parse模式的私有委託以後,咱們能夠編寫一個泛型複合函數,它須要一個泛型類型的參數、要解析的字符串值和TryParseDelegate。如今咱們能夠安全地依賴這些擴展方法,考慮如下幾點::

public class Program
{
    public static void Main(string[] args) {
        var str =
            string.Join(
                "",
                new[] { "James", "Bond", " +7 " }.Select(s => s.ToInt32()));

        Console.WriteLine(str); // 打印 "007"
    }
}

編輯注:要了解C#7的全部新功能,請查看本教程www.dotnetcurry.com/csharp/1286/csharp-7-new-expected-features

結論

這篇文章對我我的而言頗具挑戰性。我喜歡C#的許多特性,所以每次發佈只收集一個最喜歡的內容是很是困難的。

每一個較新版本的C#都包含了強大而有影響力的功能。C#語言團隊以無數的方式進行創新 - 其中之一就是引入點發布。在撰寫本文時,C# 7.1和 7.2已正式發貨。做爲C#開發人員,咱們生活在一個激動人心的語言時代!

然而,對我來講,對全部這些特性進行分類是至關有見地的;由於它幫助咱們瞭解了什麼是實際的,最影響個人平常發展。一如既往,努力成爲一個務實的開發者!並非語言中全部可用的特性都是當前任務所必需的,但瞭解什麼是可用的,這一點很重要。

當咱們期待C#8的建議和原型時,我對C#的將來感到興奮。它看起來確實頗有但願,並且語言正在積極地試圖緩解「價值億萬美金的錯誤」。

歡迎轉載,轉載請註明翻譯原文出處(本文章),原文出處(原博客地址),而後謝謝觀看

若是以爲個人翻譯對您有幫助,請點擊推薦支持:)

 

出處:https://www.cnblogs.com/chen-jie/p/csharp-favorite-features.html

相關文章
相關標籤/搜索