<NET CLR via c# 第4版> 第8章 方法

来源:http://www.cnblogs.com/harry-wang/archive/2017/05/19/6868359.html
-Advertisement-
Play Games

8.1 實例構造器和類(引用類型) 構造引用類型的對象時,在調用類型的實例構造器之前,為對象分配的記憶體總是先被歸零 。沒有被構造器顯式重寫的所有欄位都保證獲得 0 或 null 值。 構造器不能被繼承。不能使用以下修飾符: virtual,new,override,sealed和abstract. ...


8.1 實例構造器和類(引用類型)

  • 構造引用類型的對象時,在調用類型的實例構造器之前,為對象分配的記憶體總是先被歸零 。沒有被構造器顯式重寫的所有欄位都保證獲得 0 或 null 值。

  • 構造器不能被繼承。不能使用以下修飾符: virtual,new,override,sealed和abstract.

  • 如果基類沒有提供無參構造器,那麼派生類必須顯式調用一個基類構造器,否則編譯器會報錯。

  • 如果類的修飾符為 static(sealed 和 abstract),編譯器不會生成預設構造器。(靜態類在元數據中是抽象密封類)

  • 為了使代碼“可驗證”,類的實例構造器在訪問從基類繼承的任何欄位之前,必須先調用基類的構造器

  • 如果派生類的構造器沒有顯式調用一個基類構造器,C#編譯器會自動生成對預設的基類構造器的調用。

  • 極少數時候可以在不調用實例構造器的前提下創建類型的實例(如 MemberwiseClone 方法)。

  • 不要在構造器中調用虛方法。 原因是假如被實例化的類型重寫了虛方法,就會執行派生類型對虛方法的實現。但在這個時候,尚未完成對繼承層次結構中的所有欄位的初始化(被實例化的類型的構造器還沒有運行呢)。所以,調用虛方法會導致無法預測的行為。

  • 構造器中,先執行以“內聯”方式對欄位的初始化(C#編譯器將這種語法轉換成構造器方法中的代碼來執行初始化),再調用基類的構造器,最後執行當前構造器中的代碼。詳見下麵代碼:

    public class AType {
        public AType()
        {
            Console.WriteLine(nameof(AType));
        }
    }
    
    public  class Ball
    {
        public Ball()
        {
            Console.WriteLine(nameof(Ball));
        }
    }
    
    public class Basketball:Ball
    {
        //內聯語法,最終會把atype的初始化放在構造函數中
        AType atype = new AType();
        public Basketball()
        {
            Console.WriteLine(nameof(Basketball));
    
        }
    }
    public class Program
    {
        static void Main()
        {
            Basketball ball = new Basketball();
            Console.ReadLine();
        }
    }

    輸出結果為: AType,Ball,Basketball

  • 然後又做了下麵的BT測試:

    public class AType
    {
        public AType(string x)
        {
            Console.WriteLine(nameof(AType) + ":" + x);
        }
    }
    
    public class Ball
    {
        //在基類中新增加個內聯方式初始化的欄位
        private AType atype = new AType(nameof(Ball));
        public Ball()
        {
            Console.WriteLine(nameof(Ball));
        }
    }
    
    public class Basketball : Ball
    {
        //內聯語法,最終會把atype的初始化放在構造函數中
        private AType atype = new AType(nameof(Basketball));
        public Basketball()
        {
            Console.WriteLine(nameof(Basketball));
    
        }
    }
    public class Program
    {
        static void Main()
        {
            Basketball ball = new Basketball();
            Console.ReadLine();
        }
    }

    輸出結果為:AType:Basketball, AType:Ball, Ball, Basketball

  • 使用“內聯”方式初始化欄位,要註意代碼的膨脹效應。如果類中有多個構造器,編譯器會把“內聯”初始化代碼插到每一個構造器中,再插入基類構造器的調用,最後執行自己的代碼。

  • 基於上面一點,可考慮不在定義欄位時初始化,而是創建單個構造器來執行這些公共的初始化。然後,讓其他構造器利用 this 顯式調用這個公共初始化構造器,如下麵代碼:

    internal sealed class SomeType
    {
        //不要顯式初始化下麵的欄位
        private int m_x;
        private string m_s;
        private double m_d;
        private byte m_b;
    
        //該構造器將所有欄位都設為預設值,
        //其他所有構造器都顯式調用該構造器
        public SomeType()
        {
            m_x = 5;
            m_s = "Hi there";
            m_d = 3.14159;
            m_b = 0xff;
        }
    
        //該構造器將所有的欄位都設為預設值,然後修改m_x
        public SomeType(int x) : this()
        {
            m_x = x;
        }
    
        //該構造器將所有的欄位都設為預設值,然後修改m_s
        public SomeType(string s) : this()
        {
            m_s = s;
        }
    
        //該構造器將所有的欄位都設為預設值,然後修改m_x和m_s
        public SomeType(int x, string s) : this()
        {
            m_x = x;
            m_s = s;
        }
    }

8.2 實例構造器和結構(值類型)

  • C#編譯器根本不會為值類型內聯(嵌入)預設的無參構造器。結構也不能包含顯式的無參數構造函數(編譯器會報錯)。

  • 考慮到性能,CLR不會為包含在引用類型中的每個值類型欄位都主動調用構造器。值類型的實例構造器只有顯式調用才會執行。

  • 值類型的任何構造器都必須初始化值類型的全部欄位。(如未全部初始化,編譯器會報錯)

8.3 類型構造器

  • 類型構造器(靜態構造器) 可應用於介面(C#編譯器不允許)、引用類型和值類型(但請永遠不要在值類型中定義類型構造器,因為CLR有時不會調用值類型的靜態類型構造器)。

  • 類型構造器必須是 private 的,但不能顯示標記,只能由編譯器來標記為 private .

  • 類型構造器必須無參。

  • CLR保證一個類型構造器在每個AppDomain中只執行一次,而且(這種執行)是線程安全的。 所以非常適合在類型構造器中初始化類型需要的任何單實例(Singleton)對象。

8.4 操作符重載方法

  • CLR規範要求操作符重載方法必須是 publicstatic 方法。

  • C#(以及其他許多語言)要求操作符重載方法至少有一個參數的類型與當前定義這個方法的類型相同。

    public sealed class Complex
    {
        //重載“+”操作符
        public static Complex operator +(Complex c1, Complex c2){...}
    }
  • 使用不支持操作符重載的編程語言時,語言應該允許你直接調用希望的 op_* 方法(例如 op_Addition)。反之,如果在C#中引用了不支持操作符重載的語言所寫的類型,且類型中提供了一個 op_Addition 方法,這時依然不能使用+操作符來調用這個 op_Addition 方法,因為元數據中沒有關聯 specialname 標記。

8.5 轉換操作符方法

  • CLR要求操作符重載方法必須是 publicstatic 方法。

  • C#(以及其他許多語言)要求參數類型和返回類型二者必有其一與定義轉換方法的類型相同。

  • 類型轉換模板代碼:

    public sealed class Rational {
        //由一個Int32構造一個Rational
        public Rational(Int32 num) { ...}
    
        //由一個Single構造一個Rational
        public Rational(Single num) { ...}
    
        //將一個Rational轉換成一個Int32
        public Int32 ToInt32() { ...}
    
        //將一個Rational轉換成一個Single
        public Single ToSingle() { ...}
    
        //由一個Int32隱式構造並返回一個Rational
        public static implicit operator Rational(Int32 num) {
            return new Rational(num);
        }
    
        //由一個Single隱式構造並返回一個Rational
        public static implicit operator Rational(Single num) {
            return new Rational(num);
        }
    
        //由一個Rational顯示返回一個Int32
        public static explicit operator Int32(Rational r) {
            return r.ToInt32();
        }
    
        //由一個Rational顯示返回一個Single
        public static explicit operator Single(Rational r) {
            return r.ToSingle();
        }
    }
    
    //像前面那樣為 Rational 類型定義了轉換操作符之後,就可以寫出像下麵這樣的C#代碼:
    public sealed class Program
    {
        public  static void Main()
        {
            Rational r1 = 5;    //Int32 隱式轉型為 Rational
            Rational r2 = 2.5F; //Single 隱式轉型為Rational
    
            Int32 x = (Int32)r1;    //Rational 顯式轉型為Int32
            Single s = (Single)r2;  //Rational 顯式轉型為Single
        }
    }
  • implicit 關鍵字告訴編譯器可以隱式轉換; explicit 表示必須顯式轉換。

  • implicitexplicit 關鍵字之後,要指定 operator 關鍵字告訴編譯器該方法是一個轉換操作符。

  • operator 之後,指定對象要轉換成什麼為型。在圓括弧內,則指定要從什麼類型轉換。

  • 不損失精度時用 implicit ,否則用 explicit .顯式轉換失敗,應該拋出 OverflowException 或者 InvalidOperationException 異常。

8.6 擴展方法

翠花,上代碼:

    //靜態的類
    public static class StringBuilderExtensions {
        //靜態方法,this 關鍵字
        public static Int32 IndexOf(this StringBuilder sb, Char value) {
            for (Int32 index = 0; index < sb.Length; index++)
                if (sb[index] == value) return index;
            return -1;
        }
    }

現在,就可以像這樣使用Int32 index=sb.IndexOf('!')

8.6.1 規則和原則

  • C#只支持擴展方法,不支持擴展屬性、擴展事件、擴展操作符等。

  • 擴展方法(第一個參數前面有 this 的方法)必須在非泛型的靜態類中聲明。類名沒有限制。至少要有一個參數,而且只有第一個參數能用 this 關鍵字標記。

  • 如果有人在 Wintellect 命名空間中定義了一個 StringBuilderExtensions 類,那麼程式員為了訪問這個類的擴展方法,必須在他的源代碼文件頂部寫一條 using Wintellect; 指令。

  • 擴展方法可能存在版本控制問題。如果 Microsoft 未來為他們的 StringBuilder 類添加了 IndexOf 實例方法,而且和我的代碼調用的原型一樣,那麼在重新編譯我的代碼時,編譯器會綁定到Microsoft的IndexOf實例方法,而不是我的靜態IndexOf方法。這樣我的程式就會有不同的行為。

8.6.2 用擴展方法擴展各種類型

  • 為介面定義擴展方法

        public static void ShowItems<T>(this IEnumerable<T> collection) {
            foreach (var item in collection)
                Console.WriteLine(item);
        }
    
        public static void Main()
        {
            //每個Char在控制臺上單獨顯示一行
            "Grant".ShowItems();
    
            //每個String在控制臺上單獨顯示一行
            new[] { "Jeff", "Kristin" }.ShowItems();
    
            //每個Int32在控制臺上單獨顯示一行
            new List<Int32>() { 1, 2, 3 }.ShowItems();
    
        }
  • 為委托類型定義擴展方法

        public static void InvokeAndCatch<TException>(this Action<object> d, object o)
            where TException : Exception
        {
            try { d(o); }
            catch (TException) { }
        }
    
        public static void Main()
        {
            //創建一個Action委托(實例)來引用靜態 ShowItems 擴展方法。
            //並初始化第一個實參來引用字元串“Jeff”
            Action a = "Jeff".ShowItems;
    
            //調用(Invoke)委托,後者調用(call)ShowItems.
            //並向它傳遞對字元串“Jeff”的引用
            a();
        }

8.6.3 ExtensionAttribute類

8.7 分部方法

    //工具生成的代碼,存儲在某個源代碼文件中
    internal sealed partial class Base
    {
        private String m_name;

        //這是分部方法的聲明
        partial void OnNameChanging(String value);

        public String Name
        {
            get { return m_name; }
            set
            {
                OnNameChanging(value.ToUpper());    //通知類要進行更改了
                m_name = value;                     //更改欄位
            }
        }
    }

    //開發人員生成的代碼,存儲在另一個源代碼文件中:
    internal sealed partial class Base
    {
        //這是分部方法的實現,會在m_name更改前調用
        partial void OnNameChanging(string value)
        {
            if (string.IsNullOrEmpty(value))
                throw new ArgumentNullException("value");
        }
    }

規則和原則:

  • 它們只能在分部類或結構中聲明。

  • 分部方法的返回類型始終是 void ,任何參數都不能用 out 修飾符來標記,之所以如此要求,都是因為——方法可能不存在

  • 可以有 ref 參數,可以是泛型方法,可以是實例或靜態方法,而且可標記為 unsafe

  • 分部方法的聲明和實現必須具有完全一致的簽名。

  • 如果沒有對應的實現部分,便不能在代碼中創建一個委托來引用這個分部方法(編譯器會報錯)。同樣是因為——方法可能不存在

  • 分部方法總是被視為 private 方法,但C#編譯器禁止在分部方法聲明之前添加 private 關鍵字。


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

-Advertisement-
Play Games
更多相關文章
  • 很多童鞋在啟動mysql的時候,碰到過這個錯誤, 首先,澄清一點,出現這個錯誤的前提是:通過服務腳本來啟動mysql。通過mysqld_safe或mysqld啟動mysql實例並不會報這個錯誤。 那麼,出現這個錯誤的原因具體是什麼呢? 哈哈,對分析過程不care的童鞋可直接跳到文末的總結部分~ 總結 ...
  • https://ask.sqlservercentral.com/questions/16078/schema-and-role-permissions-for-all-users-in-a-dat.html ...
  • 上周由於WannaCry勒索病毒肆虐全球,14號系統管理員更新了一系列補丁後,導致一Windows Server 2012伺服器上面部署的一些老舊的應用無法使用Microsoft ODBC for Oracle訪問ORACLE資料庫。 如下所示,應用程式會提示下麵錯誤信息 The Oracle(tm... ...
  • Ubuntu中安裝Samba並添加賬戶: 1.更新源列表:sudo apt-get update 2.安裝Samba:sudo apt-get install samba samba-common 3.新建共用目錄並設置許可權 3.1 在home目錄下創建共用目錄share 在終端中輸入sudo mk ...
  • 1、 2、 3、 4、 ...
  • SHELL中的變數 運行SHELL腳本中的單個命令自然有用,但這有其自身的限制。通常你會需要在SHELL命令使用其他數據來處理信息。這可以通過變數來實現。變數允許你臨時性地將信息存儲在SHELL腳本中,以便和腳本中的其他命令一起使用。 1 環境變數 SHELL維護著一組環境變數,用了記錄特點的系統信 ...
  • 文件的壓縮與解壓 常用命令和參數 1 gzip -c 將壓縮或解壓的結果輸出至標準輸出 -d 解壓縮,相當於guzip -# (1-9)指定壓縮比,預設為6。 zcat 不解壓縮的情況下查看文件。 壓縮:gzip FileName 解壓:gzip –d FileName.gz 2 bzip2 -d ...
  • 實在受不了在Windows下編程,所以自己就安裝了一個Ubutun,公司用的翻牆軟體shadowsocks在Windows上用起來很簡單很爽,但是在Ubutun上的安裝和配置就沒那麼簡單了,寫下這篇文章,希望需要的朋友能簡單快速的搞定shadowsocks的安裝和配置 1.安裝 在命令行執行上面的三 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...