本着每天记录一点成长一点的原则,打算将目前完成的一个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 错误 } }
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; } }
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 */
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; } }
抽象类实例:
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 } }
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()); } }
泛型约束:
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; }
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; } } }
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); }
参见内联实例:
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".
详细参照: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. */
Action<string> greet = name => { string greeting = $"Hello {name}!"; Console.WriteLine(greeting); }; greet("World"); // Output: // Hello World!
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 4
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); }
元组析构:
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. }
弃元析构:
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,149
类型的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!