2017年9月25日 星期一

C# : is (C# 7.0 類型模式) 與 as 的使用練習

我們宣告了三個類別 ClassA 是最上層的基底類別,Class B 則是繼承了 ClassA,ClassC 繼承了 ClassC。
ClassA -> ClassB -> ClasssC
這三個類別,都有分別覆寫 ToString 這個方法,他們的建立程式碼如下所示。
class ClassA : ICloneable
{
    public object Clone()
    {
        return this.MemberwiseClone();
    }

    public override string ToString()
    {
        return "It is ClassA";
    }
}

class ClassB : ClassA
{
    public override string ToString()
    {
        return "It is ClassB";
    }
}

class ClassC : ClassB
{
    public override string ToString()
    {
        return "It is ClassC";
    }
}

as 的用法

從微軟官方的說明文件中,我們可以知道
使用 as 運算子,以在相容的參考型別或可為 Null 的型別之間執行特定轉換類型
在這裡,我們建立起六個物件,分別指定到型別為 object 的陣列中。
接著,我們使用 as 運算子,將執行時期的物件型別,強制轉型成為 string 這個型別,若當時物件的實際產生的型別不是 string,則,轉型出來的內容將會是空值 null。
object[] objArray = new object[6];
objArray[0] = new ClassA();
objArray[1] = new ClassB();
objArray[2] = "hello";
objArray[3] = 123;
objArray[4] = 123.4;
objArray[5] = null;

for (int i = 0; i < objArray.Length; ++i)
{
    // 在這裡進行型別轉換
    string s = objArray[i] as string;
    Console.Write("{0}:", i);
    if (s != null)
    {
        Console.WriteLine("'" + s + "'");
    }
    else
    {
        Console.WriteLine("not a string");
    }
}
底下是輸出結果
0:not a string
1:not a string
2:'hello'
3:not a string
4:not a string
5:not a string
Press any key for continuing...

is 的用法

從微軟官方的說明文件中,我們可以知道
is 運算子用於檢查物件是否與指定的類型相容
在這裡,我們分別建立 ClassA, ClassB, ClassC 這個三型別物件,在這三個物件,接著使用 is 運算子來檢驗是否與我們指定的型別運算之後,回得到 true 的回傳值。
在這裡,我們看到了,只要執行時期所產生的物件搭配 is 運算子運算之後,只要檢驗的型別,是當時物件的型別、繼承的型別、有繼承的介面,驗證結果皆會為真。
var cl1 = new ClassA();
Console.WriteLine($"cl1 is IFormatProvider : {cl1 is IFormatProvider}");
Console.WriteLine($"cl1 is ICloneable : {cl1 is ICloneable}");
Console.WriteLine($"cl1 is Object : {cl1 is Object}");
Console.WriteLine($"cl1 is ClassA : {cl1 is ClassA}");
Console.WriteLine($"cl1 is ClassB : {cl1 is ClassB}");
Console.WriteLine($"cl1 is ClassC : {cl1 is ClassC}");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();

var cl2 = new ClassB();
Console.WriteLine($"cl2 is IFormatProvider : {cl2 is IFormatProvider}");
Console.WriteLine($"cl2 is ICloneable : {cl2 is ICloneable}");
Console.WriteLine($"cl2 is Object : {cl2 is Object}");
Console.WriteLine($"cl2 is ClassA : {cl2 is ClassA}");
Console.WriteLine($"cl2 is ClassB : {cl2 is ClassB}");
Console.WriteLine($"cl2 is ClassC : {cl2 is ClassC}");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();

var cl3 = new ClassC();
Console.WriteLine($"cl3 is IFormatProvider : {cl3 is IFormatProvider}");
Console.WriteLine($"cl3 is ICloneable : {cl3 is ICloneable}");
Console.WriteLine($"cl3 is Object : {cl3 is Object}");
Console.WriteLine($"cl3 is ClassA : {cl3 is ClassA}");
Console.WriteLine($"cl3 is ClassB : {cl3 is ClassB}");
Console.WriteLine($"cl3 is ClassC : {cl3 is ClassC}");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
底下是輸出結果
cl1 is IFormatProvider : False
cl1 is ICloneable : True
cl1 is Object : True
cl1 is ClassA : True
cl1 is ClassB : False
cl1 is ClassC : False
Press any key for continuing...

cl2 is IFormatProvider : False
cl2 is ICloneable : True
cl2 is Object : True
cl2 is ClassA : True
cl2 is ClassB : True
cl2 is ClassC : False
Press any key for continuing...

cl3 is IFormatProvider : False
cl3 is ICloneable : True
cl3 is Object : True
cl3 is ClassA : True
cl3 is ClassB : True
cl3 is ClassC : True
Press any key for continuing...
我們在進行另外一個測試,那就是若產生的物件為一個衍生型別,而宣告的持有物件變數的型別為基底型別,這樣使用 is 運算子的結果會如何呢?
從底下的執行結果輸出內容可以清楚的觀察到, is 運算子會使用執行時期實際產生的物件型別,來進行型別檢驗。
Console.WriteLine($"ClassA clxl = cl2;");
ClassA clx1 = cl2;
Console.WriteLine($"ClassB clx2 = cl3;");
ClassB clx2 = cl3;
Console.WriteLine($"ClassA clx3 = cl3;");
ClassA clx3 = cl3;
Console.WriteLine($"{Environment.NewLine}");

Console.WriteLine($"clx1 is ClassA : {clx1 is ClassA}");
Console.WriteLine($"clx1 is ClassB : {clx1 is ClassB}");
Console.WriteLine($"clx1 is ClassC : {clx1 is ClassC}");
Console.WriteLine($"clx2 is ClassA : {clx2 is ClassA}");
Console.WriteLine($"clx2 is ClassB : {clx2 is ClassB}");
Console.WriteLine($"clx2 is ClassC : {clx2 is ClassC}");
Console.WriteLine($"clx3 is ClassA : {clx3 is ClassA}");
Console.WriteLine($"clx3 is ClassB : {clx3 is ClassB}");
Console.WriteLine($"clx3 is ClassC : {clx3 is ClassC}");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
底下是輸出結果
ClassA clxl = cl2;
ClassB clx2 = cl3;
ClassA clx3 = cl3;

clx1 is ClassA : True
clx1 is ClassB : True
clx1 is ClassC : False
clx2 is ClassA : True
clx2 is ClassB : True
clx2 is ClassC : True
clx3 is ClassA : True
clx3 is ClassB : True
clx3 is ClassC : True
Press any key for continuing...

類型模式執行模式比對

從微軟的官方文件中,可以得到
從 C# 7 開始,您可以搭配類型模式使用模式比對來撰寫更簡潔的程式碼,以使用 is 陳述式。
我們在這裡來使用看看,他的用法如下:
expr is type varname
例如,在底下的測試程式碼中,使用的 MyTest is ClassC e1 就是這樣的用法;若使用類型模式執行模式比對得到為否,e1 這個變數便會為空值 null。
var cl1 = new ClassA();
var cl2 = new ClassB();
var cl3 = new ClassC();

object MyTest;

// 用類型模式執行模式比對時,is 會測試運算式是否可轉換成指定的類型;如果可以的話,則會將它轉換成該類型的變數。 它是 is 陳述式的直接延伸,允許精簡類型的評估和轉換。
// https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/is#type
MyTest = cl2;
if (MyTest is ClassC e1)
{
    Console.WriteLine($"cl2 is ClassC : {e1.ToString()}");
}
if (MyTest is ClassA e2)
{
    Console.WriteLine($"cl2 is ClassA : {e2.ToString()}");
}
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
底下是輸出結果
cl2 is ClassA : It is ClassB
Press any key for continuing...

Press any key for continuing...

C# : 練習如何設計 多載 Overloading 與 覆寫 Overriding

相信很多人對於 C# 的多載 Overloading 與 覆寫 Overriding 都無法了解到其中的差異,我們在這裡做個簡單的說明


了解更多關於 [成員多載的使用方式
了解更多關於 [override (C# 參考)的使用方式
了解更多關於 [C# 程式設計手冊]


  • 多載 Overloading
    這是在同一個型別中,對於同一個函式的名稱,卻有不同的參數類型與數量(不含回傳值的型別),此時,要呼叫這個函式的時候,就需要根據所傳入的引數來判斷;通常在 Visual Studio 內,會有 IntelliSense 來幫助我們來選擇可以使用那些函式。
    多載 Overloading
    這是在建置時間就會決定的。
  • 覆寫 Overriding
    在類別繼承關係中,在基底類別與衍生類別中,都會使用到相同函式簽章的函式,因此,在執行時期,需要明確指出要使用基底類別或者衍生類別的哪個函式來執行。
    這是在執行時期才會決定的

多載 Overloading

首先我們先來看看 多載 Overloading 的使用與運作情況,在底下我們建立了一個類別,在這個類別中,有兩個同名的函式,都是命名為 Hello。
不過,這兩個同名的函式,卻宣告使用不同的參數數量與類型,因此,這裡將會套用到多載這樣的觀念。
public class MyClass
{
    public void Hello()
    {
        Console.WriteLine("Who are you");
    }
    public void Hello(string name)
    {
        Console.WriteLine($"Hello {name}");
    }
}
現在,讓我們來實際使用這個 Hello 方法,從底下的測試程式碼中,我們將會呼叫兩個不同的 Hello 函式,並且可以從輸出結果內容中,得到驗證,確實多載方法有被呼叫到。
Console.WriteLine($"多載 Overloading 的使用");
MyClass myClass = new MyClass();
myClass.Hello();
myClass.Hello("Vulcan");
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
這是執行結果的輸出內容
多載 Overloading 的使用
Who are you
Hello Vulcan
Press any key for continuing...

覆寫 Overriding

接下來,我們來看看覆寫 Overriding如何使用。
我們先建立兩個類別,一個是基底類別 MyBaseClass,另外一個是繼承前面基底類別而產生的衍生類別 MyDerivedClass。在這兩個類別,都宣告了同樣函式簽章的方法,只不過,在基底類別的 Hello 方法前面,有使用 virtual (這表示可用來修改方法、屬性、索引子或事件宣告,並可在衍生類別中受到覆寫),並且,我們也在衍生類別的 Hello 方法前面使用了 override (能夠擴充或修改繼承方法、屬性、索引子或事件的抽象或虛擬實作)。
public class MyBaseClass
{
    public virtual void Hello()
    {
        Console.WriteLine("Hello MyBaseClass");
    }
}

public class MyDerivedClass : MyBaseClass
{
    public override void Hello()
    {
        Console.WriteLine("Hello MyDerivedClass");
    }
}
我們在這裡建立三個物件,前兩的物件的宣告型別與實際產生物件的型別都是一致的,因此,我們可以知道他們所需要的 Hello 方法,就是當時所宣告與產生物件型別的定義方法。
不過,對於第三個物件 myDerivedBaseClass,我們使用底下說明的內容建立起來
  • 物件的型別為 MyBaseClass
    MyBaseClass myDerivedBaseClass
  • 產生物件的型別為 MyDerivedClass
    new MyDerivedClass()
因此,當要執行這個 myDerivedBaseClass.Hello(); 函式執行的時候,在執行時期,CLR 會檢查當時的物件宣告型別,若該宣告型別的函示沒有被覆蓋過,則就會直接執行當時宣告該物件型別內定義的方法;然而,若在實際建立起來的物件型別內,有覆蓋這個方法定義,則此時就會呼叫被覆蓋的方法。
在這個練習範例中,物件的宣告型別為 MyBaseClass,實際產生的物件型別為 MyDerivedClass,因為這個衍生類別的 Hello 方法,有使用 override 覆寫方法定義,所以,在實際執行的時候,不論宣告的型別是基底或者衍生類別,都會直接執行衍生類別中的 Hello 方法。
各位可以嘗試看看,若將基底類別的 virtual 與衍生類別的 override 關鍵字刪除掉,再度執行同樣的測試程式碼,會產生甚麼樣的狀況嗎?
Console.WriteLine($"覆寫 Overriding 的使用");
MyBaseClass myBaseClass = new MyBaseClass();
MyDerivedClass myDerivedClass = new MyDerivedClass();
MyBaseClass myDerivedBaseClass = new MyDerivedClass();
myBaseClass.Hello();
myDerivedClass.Hello();
myDerivedBaseClass.Hello();
Console.WriteLine($"Press any key for continuing...{Environment.NewLine}");
Console.ReadKey();
這是執行結果的輸出內容
覆寫 Overriding 的使用
Hello MyBaseClass
Hello MyDerivedClass
Hello MyDerivedClass
Press any key for continuing...

了解更多關於 [成員多載的使用方式
了解更多關於 [override (C# 參考)的使用方式
了解更多關於 [C# 程式設計手冊]






2017年9月24日 星期日

Visual Studio : 除錯模式,會使用預設的帳號與密碼

我們經常會遇到這樣的開發情境,在開發的應用程式中,一啟動這個應用程式,需要先進行登入,輸入帳號與密碼,才能夠進入到首頁;可是,每次要進行專案除錯的時候,都需要重覆的輸入正確的帳號與密碼,才能夠繼續進行相關作業執行與除錯的工作。
這樣的開發模式顯得相當沒有效率,因此,我們可以使用 組態管理員 預設提供的兩個建置模式,分別為 Debug 與 Release 的模式;在 Debug 模式下,會自動幫我們產生一個條件式編譯的符號 DEBUG,而我們可以 C# 程式碼內,使用 C# 前置處理器指示詞 #if #else #endif 等等,就可以指定在除錯模式或者正式產品建置模式,該執行那些程式碼。
因此,我們就可以透過這樣的機制,解決我們在除錯模式下,不用自行輸入帳號與密碼,而是讓我們使用自行預設的帳號與密碼來執行相關流程。
    class Program
    {
        static void Main(string[] args)
        {
            string Account = "";
            string Password = "";

#if DEBUG
            Account = "Vulcan";
            Password = "123456";
#else
            Console.WriteLine("請輸入帳號:");
            Account = Console.ReadLine();
            Console.WriteLine("請輸入密碼:");
            Password = Console.ReadLine();
#endif

            Console.WriteLine($"你輸入的帳號與密碼為 {Account} / {Password}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }
在上面的程式碼是我們這次要進行測試的內容,我們使用 C# 前置處理器指示詞 #if DEBUG 來確認此次建置與執行的過程,是否在除錯模式下;若為除錯模式,則你將會看到如下圖的顏色標示,在 #if DEBUG 條件成立的區塊程式碼,會有顏色標示出來,這表示,此次執行過程中,這些程式碼將會被執行,也就是,帳號與密碼這兩個變數,將會使用我們預設的數值。
Debug Build
若為 Release 模式下,您會看到下圖,此時,在執行過程中,將需要由使用者輸入帳號與密碼之後,才能夠繼續進行程式碼執行,您可以從程式碼是否為灰色看出,那些程式碼,在那些建智狀態下,可以執行或者不能執行。
Release Build
最後,我們將帳號與密碼顯示出來,確認我們的執行過程正確無誤。

C# : 使用 ICloneable 介面,複製一個物件

除了可以使用建構函式來進行複製一個物件,現在,讓我們來使用 C# 提供的一個介面設計模式,ICloneable,只要在類別中實作出這個介面,你設計的類別就會具有複製一個物件的能力。

了解更多關於 [ICloneable Interface
了解更多關於 [C# 程式設計手冊 



我們使用前面的建構函式的類別範例程式碼,Person,接著,在這個類別中,實作 ICloneable 介面;這個介面相當的簡單,你只需要實作出 Clone 這個方法即可。
在這裡,我們使用 System.Object 型別中的 MemberwiseClone 方法來做到物件複製的效果,MemberwiseClone 方法提供了物件淺層複製的能力,因此,在執行這個方法之後,就會建立新的物件,並再將目前物件的非靜態欄位複製到新的物件建立淺層複本,這裡產生的新物件則會在堆積記憶體上產生另外一個新的物件記憶體空間。
    class Person : ICloneable
    {
        public int Age { get; set; }
        public string Name { get; set; }

        // 宣告一個建構函式,並且進行物件成員值的初始化
        public Person(string name, int age)
        {
            Name = name;
            Age = age;
        }

        // 覆蓋 object.ToString 方法,顯示該物件的詳細資料
        public override string ToString()
        {
            return Name + " is " + Age.ToString();
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }

進行測試

在底下的程式碼中,我們首先建立一個物件,person1,並且設定這個物件的 Name / Age 這兩個屬性值。
接著,我們使用複製物件的 Clone() 函式,我們就得到一個內容完全相同的物件,並且 person1 與 person2 分別指向到不同的堆積記憶體空間內。


然後,為了要確認 person1 與 person2 分別指向不同的記憶體空間,我們使用將 person1 / person2 的屬性值進行修改,並且將這兩個物件的屬性值顯示出來,就可以確認這兩個物件是在不同的堆積記憶體空間中。
    class Program
    {
        static void Main(string[] args)
        {
            // 建立一個 Person 物件,且於建構函式引數內,指定要初始化的成員值
            Person person1 = new Person("George", 40);

            // 使用複製建構函式,產生一個成員值完全相同的 Person 物件
            Person person2 = person1.Clone() as Person;

            Console.WriteLine("person1 / person2 兩個物件成員值");
            Console.WriteLine($"person1 : {person1.ToString()}");
            Console.WriteLine($"person2 : {person2.ToString()}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();

            Console.WriteLine("進行修改 person1 / person2 兩個物件成員值");
            person1.Age = 39;
            person2.Age = 41;
            person2.Name = "Charles";

            Console.WriteLine("person1 / person2 兩個物件成員值");
            Console.WriteLine($"person1 : {person1.ToString()}");
            Console.WriteLine($"person2 : {person2.ToString()}");
            Console.WriteLine("Press any key for continuing...");
            Console.ReadKey();
        }
    }

了解更多關於 [ICloneable Interface
了解更多關於 [C# 程式設計手冊