Prism+MaterialDesign+EntityFramework Core+Postgresql WPF開發總結 之 基礎篇
本著每天記錄一點成長一點的原則,打算將目前完成的一個WPF專案相關的技術分享出來,供團隊學習與總結。
總共分三個部分:
基礎篇主要爭對C#初學者,鞏固C#常用知識點;
中級篇主要爭對WPF佈局與美化,在減輕程式碼量的情況做出漂亮的應用;
終極篇為框架應用實戰,包含MVVM框架Prism,ORM框架EntityFramework Core,開源資料庫Postgresql。
目錄
- Prism+MaterialDesign+EntityFramework Core+Postgresql WPF開發總結 之 基礎篇
- Prism+MaterialDesign+EntityFramework Core+Postgresql WPF開發總結 之 中級篇(待續)
- Prism+MaterialDesign+EntityFramework Core+Postgresql WPF開發總結 之 終極篇(待續)
前言
此篇為C#常用知識點的例項說明,如果你是多年C#開發者可以跳過此篇或者只關注最後的新特性。
1、OOP之源 類與例項
一切事物皆物件。
類像產品模版,用它可以生產很多產品(簡稱例項物件)。
類:具有相同屬性和行為的物件的抽象集合。例項物件:具備一組可識別的特性與行為的實體。
舉個例子:張三、李四。
幻化出類如下:屬性為名字,例項就是張三、李四。
public class Person { public string Name { get; set; } public Person(string name) { Name = name; } } 張三=new Person("張三") 李四=new Person("李四")
類與屬性的修飾符中需要特別關注如下三個:
sealed:密封效果
- 修飾類時,類將不可以作為基類繼承。
- 修飾屬性與行為時,屬性與行為在繼承類中無法Override與New。
sealed class SealedClass { public int x; public int y; } // error class SealedTest2:SealedClass class SealedTest2 { static void Main() { var sc = new SealedClass(); sc.x = 110; sc.y = 150; Console.WriteLine($"x = {sc.x}, y = {sc.y}"); } } // Output: x = 110, y = 150 //------------------ class X { protected virtual void F() { Console.WriteLine("X.F"); } protected virtual void F2() { Console.WriteLine("X.F2"); } } class Y : X { sealed protected override void F() { Console.WriteLine("Y.F"); } protected override void F2() { Console.WriteLine("Y.F2"); } } class Z : Y { // Attempting to override F causes compiler error CS0239. // protected override void F() { Console.WriteLine("Z.F"); } // Overriding F2 is allowed. protected override void F2() { Console.WriteLine("Z.F2"); } }
internal:程式集訪問控制
- 只有在同一程式集的檔案中,內部型別或成員才可訪問。
- 可以修飾類與成員,通常用於元件開發。
// Assembly1.cs // Compile with: /target:library internal class BaseClass { public static int intM = 0; } // Assembly1_a.cs // Compile with: /reference:Assembly1.dll class TestAccess { static void Main() { var myBase = new BaseClass(); // CS0122 錯誤 } }View Code
protected:成員訪問控制
- 只能修飾成員,不可以修飾類。
- 修飾的成員,派生類內部可以直接訪問。
class A { protected int x = 123; } class B : A { static void Main() { var a = new A(); var b = new B(); // Error CS1540, because x can only be accessed by // classes derived from A. // a.x = 10; // OK, because this class derives from A. b.x = 10; } }View Code
2、OOP之三大特性:
1、封裝
例項化的每個物件都包含它能進行操作所需要的全部資訊。
好處:減少耦合,在類結構變化時建立的物件可以跟隨變化;設定訪問限制,對外介面清晰明瞭。
2、繼承 (is-a關係)
站在巨人的肩膀上才能飛得更高。
通過繼承,除了被繼承類的特性之外,可以通過重寫、新增和修改建立自己的獨有特性。由於父子關係的原因導致類之間強耦合,最好在is-a關係時使用,has-a關係就不行了。
- New修飾:顯式指示成員不應作為基類成員的重寫。
- 抽象方法:abstract 修飾,必須在直接繼承自該類的任何非抽象類中重寫該方法。 如果派生類本身是抽象的,則它會繼承抽象成員而不會實現它們。
- 虛方法:virtual修飾,派生類可以根據需要重寫該方法,也可以直接使用基類的實現。
3、多型
程式碼使用基類方法,實際執行時呼叫派生類物件的重寫方法。
相當於派生類的物件可以作為基類的物件處理。 在出現此多形性時,該物件的宣告型別不再與執行時型別相同。
public class Shape { // A few example members public int X { get; private set; } public int Y { get; private set; } public int Height { get; set; } public int Width { get; set; } // Virtual method public virtual void Draw() { Console.WriteLine("Performing base class drawing tasks"); } } public class Circle : Shape { public override void Draw() { // Code to draw a circle... Console.WriteLine("Drawing a circle"); base.Draw(); } } public class Rectangle : Shape { public override void Draw() { // Code to draw a rectangle... Console.WriteLine("Drawing a rectangle"); base.Draw(); } } public class Triangle : Shape { public override void Draw() { // Code to draw a triangle... Console.WriteLine("Drawing a triangle"); base.Draw(); } } // Polymorphism at work #1: a Rectangle, Triangle and Circle // can all be used whereever a Shape is expected. No cast is // required because an implicit conversion exists from a derived // class to its base class. var shapes = new List<Shape> { new Rectangle(), new Triangle(), new Circle() }; // Polymorphism at work #2: the virtual method Draw is // invoked on each of the derived classes, not the base class. foreach (var shape in shapes) { shape.Draw(); } /* Output: Drawing a rectangle Performing base class drawing tasks Drawing a triangle Performing base class drawing tasks Drawing a circle Performing base class drawing tasks */View Code
3、介面與抽象類
在設計的時候有時候很難決定用那種。原則有一個:如果不想波及子類的修改就用抽象類。因為介面定義之後,繼承的類必須實現此介面。
- 介面:包含方法、 屬性、 事件和索引器,成員隱式都具有公共訪問許可權,介面只定義不實現成員。一個介面可能從多個基介面繼承,類或結構可以實現多個介面。
- 抽象類:包含抽象方法,虛方法,特有行為,屬性,成員的訪問許可權可控制。sealed不可以使用,且只能繼承一個抽象類。
介面例項:
public delegate void StringListEvent(IStringList sender); public interface IStringList { void Add(string s); int Count { get; } event StringListEvent Changed; string this[int index] { get; set; } }View Code
抽象類例項:
abstract class A { public abstract void F(); } abstract class B: A { public void G() {} } class C: B { public override void F() { // actual implementation of F } }View Code
4、泛型
在客戶端程式碼宣告並初始化這些類或方法之前,這些類或方法會延遲指定一個或多個型別。不會產生執行時轉換或裝箱操作的成本或風險。
- 使用泛型型別可以最大限度地重用程式碼、保護型別安全性以及提高效能。
- 泛型最常見的用途是建立集合類。
- 可以建立自己的泛型介面、泛型類、泛型方法、泛型事件和泛型委託。
- 可以對泛型類進行約束以訪問特定資料型別的方法。
// Declare the generic class. public class GenericList<T> { public void Add(T input) { } } class TestGenericList { private class ExampleClass { } static void Main() { // Declare a list of type int. GenericList<int> list1 = new GenericList<int>(); list1.Add(1); // Declare a list of type string. GenericList<string> list2 = new GenericList<string>(); list2.Add(""); // Declare a list of type ExampleClass. GenericList<ExampleClass> list3 = new GenericList<ExampleClass>(); list3.Add(new ExampleClass()); } }View Code
泛型約束:
class Base { } class Test<T, U> where U : struct where T : Base, new() { }
where T : notnull 指定型別引數必須是不可為 null 的值型別或不可為 null 的引用型別(C# 8.0)
where T : unmanaged 指定型別引數必須是不可為 null 的非託管型別(C# 7.3).通過 unmanaged
約束,使用者能編寫可重用例程,從而使用可作為記憶體塊操作的型別。
unsafe public static byte[] ToByteArray<T>(this T argument) where T : unmanaged { var size = sizeof(T); var result = new Byte[size]; Byte* p = (byte*)&argument; for (var i = 0; i < size; i++) result[i] = *p++; return result; }View Code
5、擴充套件方法
擴充套件方法使你能夠向現有型別“新增”方法,而無需建立新的派生型別、重新編譯或以其他方式修改原始型別。 擴充套件方法是一種特殊的靜態方法,但可以像擴充套件型別上的例項方法一樣進行呼叫。Dapper,System.Linq(對System.Collections.IEnumerable 和 System.Collections.Generic.IEnumerable<T> 的擴充套件)就是經典使用。
擴充套件方法的優先順序總是比型別本身中定義的例項方法低,且只能訪問公有成員。 只在不得已的情況下才實現擴充套件方法。
如果確實為給定型別實現了擴充套件方法,請記住以下幾點:
-
如果擴充套件方法與該型別中定義的方法具有相同的簽名,則擴充套件方法永遠不會被呼叫。
-
在名稱空間級別將擴充套件方法置於範圍中。 例如,如果你在一個名為
Extensions
的名稱空間中具有多個包含擴充套件方法的靜態類,則這些擴充套件方法將全部由using Extensions;
指令置於範圍中。
namespace ExtensionMethods { public static class MyExtensions { public static int WordCount(this String str) { return str.Split(new char[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length; } } }View Code
6、語言整合查詢(LINQ)
一系列直接將查詢功能整合到 C# 語言的技術統稱。 傳統上,針對資料的查詢都以簡單的字串表示,而沒有編譯時型別檢查或 IntelliSense 支援。 此外,還需要針對每種資料來源學習一種不同的查詢語言:SQL 資料庫、XML 文件、各種 Web 服務等等。 藉助 LINQ,查詢成為了最高階的語言構造,就像類、方法和事件一樣。
簡單點就是使用查詢表示式可以查詢和轉換 SQL 資料庫、ADO .NET 資料集、XML 文件和流以及 .NET 集合中的資料。超越了限定於集合的擴充套件方法System.Linq。Entity Framework就是基於這個自動生成查詢Sql操作資料。
-
查詢表示式中的變數全都是強型別,儘管在許多情況下,無需顯式提供型別,因為編譯器可以推斷出。 有關詳細資訊,請參閱 LINQ 查詢操作中的型別關係。
-
只有在迴圈訪問查詢變數後,才會執行查詢(例如,在
foreach
語句中)。 有關詳細資訊,請參閱 LINQ 查詢簡介。 - 應用程式始終將源資料視為 IEnumerable<T> 或 IQueryable<T> 集合。
查詢表示式:
查詢表示式必須以 from 子句開頭,且必須以 select 或 group 子句結尾。 在第一個 from
子句與最後一個 select
或 group
子句之間,可以包含以下這些可選子句中的一個或多個:where、orderby、join、let,甚至是其他 from 子句。 還可以使用 into 關鍵字,使 join
或 group
子句的結果可以充當相同查詢表示式中的其他查詢子句的源。
// percentileQuery is an IEnumerable<IGrouping<int, Country>> var percentileQuery = from country in countries let percentile = (int) country.Population / 10_000_000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup; // grouping is an IGrouping<int, Country> foreach (var grouping in percentileQuery) { Console.WriteLine(grouping.Key); foreach (var country in grouping) Console.WriteLine(country.Name + ":" + country.Population); }View Code
參見內聯例項:
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } class Cat : Pet { } class Dog : Pet { } public static void MultipleJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Person rui = new Person { FirstName = "Rui", LastName = "Raposo" }; Person phyllis = new Person { FirstName = "Phyllis", LastName = "Harris" }; Cat barley = new Cat { Name = "Barley", Owner = terry }; Cat boots = new Cat { Name = "Boots", Owner = terry }; Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte }; Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui }; Cat daisy = new Cat { Name = "Daisy", Owner = magnus }; Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis }; Dog duke = new Dog { Name = "Duke", Owner = magnus }; Dog denim = new Dog { Name = "Denim", Owner = terry }; Dog wiley = new Dog { Name = "Wiley", Owner = charlotte }; Dog snoopy = new Dog { Name = "Snoopy", Owner = rui }; Dog snickers = new Dog { Name = "Snickers", Owner = arlene }; // Create three lists. List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui, phyllis }; List<Cat> cats = new List<Cat> { barley, boots, whiskers, bluemoon, daisy }; List<Dog> dogs = new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy, snickers }; // The first join matches Person and Cat.Owner from the list of people and // cats, based on a common Person. The second join matches dogs whose names start // with the same letter as the cats that have the same owner. var query = from person in people join cat in cats on person equals cat.Owner join dog in dogs on new { Owner = person, Letter = cat.Name.Substring(0, 1) } equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) } select new { CatName = cat.Name, DogName = dog.Name }; foreach (var obj in query) { Console.WriteLine( $"The cat \"{obj.CatName}\" shares a house, and the first letter of their name, with \"{obj.DogName}\"."); } } // This code produces the following output: // // The cat "Daisy" shares a house, and the first letter of their name, with "Duke". // The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".View Code
詳細參照:https://docs.microsoft.com/zh-cn/dotnet/csharp/linq/
7、lambda表示式
匿名函式演變而來。匿名函式是一個“內聯”語句或表示式,可在需要委託型別的任何地方使用。 可以使用匿名函式來初始化命名委託,或傳遞命名委託(而不是命名委託型別)作為方法引數。
在 C# 1.0 中,通過使用在程式碼中其他位置定義的方法顯式初始化委託來建立委託的例項。 C# 2.0 引入了匿名方法的概念,作為一種編寫可在委託呼叫中執行的未命名內聯語句塊的方式。 C# 3.0 引入了 lambda 表示式,這種表示式與匿名方法的概念類似,但更具表現力並且更簡練。任何 Lambda 表示式都可以轉換為委託型別。 Lambda 表示式可以轉換的委託型別由其引數和返回值的型別定義。 如果 lambda 表示式不返回值,則可以將其轉換為 Action
委託型別之一;否則,可將其轉換為 Func
委託型別之一。
class Test { delegate void TestDelegate(string s); static void M(string s) { Console.WriteLine(s); } static void Main(string[] args) { // Original delegate syntax required // initialization with a named method. TestDelegate testDelA = new TestDelegate(M); // C# 2.0: A delegate can be initialized with // inline code, called an "anonymous method." This // method takes a string as an input parameter. TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); }; // C# 3.0. A delegate can be initialized with // a lambda expression. The lambda also takes a string // as an input parameter (x). The type of x is inferred by the compiler. TestDelegate testDelC = (x) => { Console.WriteLine(x); }; // Invoke the delegates. testDelA("Hello. My name is M and I write lines."); testDelB("That's nothing. I'm anonymous and "); testDelC("I'm a famous author."); // Keep console window open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } /* Output: Hello. My name is M and I write lines. That's nothing. I'm anonymous and I'm a famous author. Press any key to exit. */View Code
Action<string> greet = name => { string greeting = $"Hello {name}!"; Console.WriteLine(greeting); }; greet("World"); // Output: // Hello World!View Code
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index); Console.WriteLine(string.Join(" ", firstSmallNumbers)); // Output: // 5 4View Code
8、元組
使用之前必須新增 NuGet 包 System.ValueTuple,
C# 7.0新增的新功能。
元組主要作為返回多個值方法的返回值,簡化定義返回值型別的麻煩。與類和結構一樣,使用資料結構儲存多個元素,但不定義行為。 既可以獲得靜態型別檢查的所有好處,又不需要使用更復雜的 class
或 struct
語法來創作型別。元組還是對 private
或 internal
這樣的實用方法最有用。不過公共方法返回具有多個元素的值時,請建立使用者定義的型別(class
或 struct
型別)。
public static double StandardDeviation(IEnumerable<double> sequence) { var computation = ComputeSumAndSumOfSquares(sequence); var variance = computation.SumOfSquares - computation.Sum * computation.Sum / computation.Count; return Math.Sqrt(variance / computation.Count); } private static (int Count, double Sum, double SumOfSquares) ComputeSumAndSumOfSquares(IEnumerable<double> sequence) { double sum = 0; double sumOfSquares = 0; int count = 0; foreach (var item in sequence) { count++; sum += item; sumOfSquares += item * item; } return (count, sum, sumOfSquares); }View Code
元組析構:
public static void Main() { (string city, int population, double area) = QueryCityData("New York City"); // Do something with the data. } public static void Main() { var (city, population, area) = QueryCityData("New York City"); // Do something with the data. } public static void Main() { (string city, var population, var area) = QueryCityData("New York City"); // Do something with the data. } public static void Main() { string city = "Raleigh"; int population = 458880; double area = 144.8; (city, population, area) = QueryCityData("New York City"); // Do something with the data. }View Code
棄元析構:
using System; using System.Collections.Generic; public class Example { public static void Main() { var (_, _, _, pop1, _, pop2) = QueryCityDataForYears("New York City", 1960, 2010); Console.WriteLine($"Population change, 1960 to 2010: {pop2 - pop1:N0}"); } private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int year2) { int population1 = 0, population2 = 0; double area = 0; if (name == "New York City") { area = 468.48; if (year1 == 1960) { population1 = 7781984; } if (year2 == 2010) { population2 = 8175133; } return (name, area, year1, population1, year2, population2); } return ("", 0, 0, 0, 0, 0); } } // The example displays the following output: // Population change, 1960 to 2010: 393,149View Code
型別的Deconstruct使用者自定義析構:
using System; public class Person { public string FirstName { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } public string City { get; set; } public string State { get; set; } public Person(string fname, string mname, string lname, string cityName, string stateName) { FirstName = fname; MiddleName = mname; LastName = lname; City = cityName; State = stateName; } // Return the first and last name. public void Deconstruct(out string fname, out string lname) { fname = FirstName; lname = LastName; } public void Deconstruct(out string fname, out string mname, out string lname) { fname = FirstName; mname = MiddleName; lname = LastName; } public void Deconstruct(out string fname, out string lname, out string city, out string state) { fname = FirstName; lname = LastName; city = City; state = State; } } public class Example { public static void Main() { var p = new Person("John", "Quincy", "Adams", "Boston", "MA"); // Deconstruct the person object. var (fName, lName, city, state) = p; Console.WriteLine($"Hello {fName} {lName} of {city}, {state}!"); } } // The example displays the following output: // Hello John Adams of Boston, MA!View Code