<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
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...