作为平时的学习记录,内容大多来自微软官方文档以及《C#图解教程》(第五版)。
1.字符串内插
在字符串左引号前添加$,可以在大括号 之间的字符串内包括变量。例如
Console.WriteLine($"Hello {aFriend}");
2.几个方法
TrimStart、TrimEnd删除空格
Replace替换
Contains搜索包含
StartWith、EndWith搜索开头结尾
dotnet x.dll

Install-Package Newtonsoft.Json
 项目中使用using引进包,JsonConvert.SerializeObject方法将对象转换为可人工读取的字符串。
项目中使用using引进包,JsonConvert.SerializeObject方法将对象转换为可人工读取的字符串。
public Calculator()
   {
       StreamWriter logFile = File.CreateText("calculator.log");
       Trace.Listeners.Add(new TextWriterTraceListener(logFile));
       Trace.AutoFlush = true;
       Trace.WriteLine("Starting Calculator Log");
       Trace.WriteLine(String.Format("Started {0}", System.DateTime.Now.ToString()));
   }
把之前用来计算的静态方法改为成员方法,在每个计算后添加写入日志的代码。运行程序后,在文件目录下找到calculator.log。
public void Finish()
 {
   writer.WriteEndArray();
   writer.WriteEndObject();
   writer.Close();
 }
与之前的简单安卓app类似。
计时数学测验没啥好说的。      
匹配游戏MatchingGames
var userName = "pyq";
var stringWithEscapes = $"C:\\Users\\{userName}\\Documents";
var verbatimInterpolated = $@"C:\Users\{userName}\Documents";
Console.WriteLine(stringWithEscapes);
Console.WriteLine(verbatimInterpolated);
// C:\Users\pyq\Documents
// C:\Users\pyq\Documents
var cultures = new System.Globalization.CultureInfo[]
{
    System.Globalization.CultureInfo.GetCultureInfo("en-US"),
    System.Globalization.CultureInfo.GetCultureInfo("en-GB"),
    System.Globalization.CultureInfo.GetCultureInfo("nl-NL"),
    System.Globalization.CultureInfo.InvariantCulture
};
var date = DateTime.Now;
var number = 31_415_926.536;
FormattableString message = $"{date,20}{number,20:N3}";
foreach (var culture in cultures)
{
    var cultureSpecificMessage = message.ToString(culture);
    Console.WriteLine($"{culture.Name,-10}{cultureSpecificMessage}");
}
// Expected output is like:
// en-US       5/17/18 3:44:55 PM      31,415,926.536
// en-GB      17/05/2018 15:44:55      31,415,926.536
// nl-NL        17-05-18 15:44:55      31.415.926,536
//            05/17/2018 15:44:55      31,415,926.536
0 索引与 sequence[0] 相同。 ^0 索引与 sequence[sequence.Length] 相同。 表达式 sequence[^0] 不会引发异常,就像 sequence[sequence.Length] 一样。 对于任何数字 n,索引 ^n 与 sequence[sequence.Length - n] 相同。
string[] words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0
CTS(通用类型系统)中值类型和引用类型

引用类型:类、委托、数组或接口。   
声明变量时一直包含值null,直到new对象或分配其他对象。创建对象后,内存会在托管堆上进行分配,并且变量只保留对对象位置的引用。
int i = 123;      // a value type
object o = i;     // boxing
int j = (int)o;   // unboxing

装箱时生成的是全新的引用对象,会有时间损耗,造成效率降低。所以应尽量避免装箱。一般可通过重载函数和泛型避免。
建议不要直接在全局命名空间中声明委托类型(或其他类型)。
委托是一个类,它定义了方法的类型,使得可以将方法当作另一个方法的参数来进行传递,这种将方法动态地赋给参数的做法,可以避免在程序中大量使用If-Else(Switch)语句,同时使得程序具有更好的可扩展性。
使用委托可以将多个方法绑定到同一个委托变量,当调用此变量时(这里用“调用”这个词,是因为此变量代表一个方法),可以依次调用所有绑定的方法。
声明和定义委托分为4个步骤:
1.声明一个委托(函数指针)
2.创建委托对象
3.创造符合委托格式的函数。(指针指向的函数)
4.将函数名称赋值给委托。
更详细的 解释和实例
事件是后期绑定机制,事件是对象用于(向系统中的所有相关组件)广播已发生事情的一种方式。 任何其他组件都可以订阅事件,并在事件引发时得到通知。
定义事件
public event EventHandler<FileLisArgs> Progress;
引发事件时,使用委托调用语法调用事件
Process?.Invoke(this, new FileLisArgs(file));
//?. 运算符可确保在事件没有订阅服务器时不引发事件。
使用 += 运算符订阅事件, -=取消订阅。
class Program
        {
            static void MyMethod(MyClass f1, int f2)
            {
                f1.Val = f1.Val + 5;
                f2 = f2 + 5;
                Console.WriteLine($"f1.Val:{f1.Val}, f2:{f2}");
            }
            static void Main()
            {
                MyClass a1 = new MyClass();
                int a2 = 10;
                MyMethod(a1, a2);
                Console.WriteLine($"a1.Val:{a1.Val},a2:{a2}");
            }
        }
//f1.Val:25, f2:15
//a1.Val:25, a2:10
//f1.Val:25, f2:15
//a1.Val:25, a2:15
class MyClass
        {
            public int Val = 20;
        }
        class Program
        {
            static void RefAsParameter(MyClass f1)
            {
                f1.Val = 50;
                Console.WriteLine($"After member assignment: {f1.Val}");
                f1 = new MyClass();
                Console.WriteLine($"After new object creation: {f1.Val}");
            }
            static void Main()
            {
                MyClass a1 = new MyClass();
                Console.WriteLine($"Before Method call: {a1.Val}");
                RefAsParameter(a1);
                Console.WriteLine($"After method call: {a1.Val}");
            }
        }
//Before Method call: 20
//After member assignment: 50
//After new object creation: 20
//After method call: 50
Before Method call: 20
After member assignment: 50
After new object creation: 20
After method call: 20
和引用参数不同的地方
//原来的写法
 static void Main()
            {
                MyClass a1 = new MyClass();
                int a2;
                MyMethod(out a1, out a2);
            }
//新的写法
static void Main()
            {
                MyMethod(out MyClass a1, out int a2);
            }
    private int firstField;
    //或写成_firstField
    public  int FirstField;
    public MyDerivedClass(int x, string s):base(s,x){
        ……
    }
2、使用this并指明使用当前类的哪一个构造函数
    public MyClass(int x):this(x,"using default string"){
        ……
    }
这种语法的好处是,如果一个类有好几个构造函数,并且都需要在对象构造过程开始时执行一些公共的代码,就可以提取公共代码作为一个公共构造函数(设置为public),被其他所有的构造函数用作构造函数初始化语句。
五种访问级别如图所示


    static class ExtendMyData{
        public static double Average(this MyData md){
            ……
        }
    }
    char c1 = "d";//单个字符
    char c2 = "\n";//简单转义序列
    char c3 = "\x0061";//十六进制转义序列
    char c4 = "\u005a";//Unicode转义序列
    
使用“短路”模式操作,如果计算Expr1之后结果已经确定了,将会跳过Expr2的求值。因此不要在Expr2中放置带副作用的表达式(比如改变一个值),因为可能不会计算。
    public static implicit operator TargetType(SourceType Identifier){
        ……
        return ObjectOfTargetType;
    }
  public static explicit operator TargetType(SourceType Identifier){
      ……
      return ObjectOfTargetType;
  }
使用explicit运算符时,需要显式使用转换运算符,也就是把想要把表达式转换成的目标类型的名称组成,放在一对圆括号内部。
class SomeClass
    {
        public int Field1;
        public int Field2;
        public void Method1() { }
        public int Method2() { return 1; }
    }
    class Program
    {
        static void Main()
        {
            Type t = typeof(SomeClass);
            FieldInfo[] fi = t.GetFields();
            MethodInfo[] mi = t.GetMethods();
            foreach (FieldInfo f in fi)
                Console.WriteLine($"Field: {f.Name}");
            foreach (MethodInfo m in mi)
                Console.WriteLine($"Method: {m.Name}");
        }
    }
输出结果为
Field: Field1
Field: Field2
Method: Method1
Method: Method2
Method: GetType
Method: ToString
Method: Equals
Method: GetHashCode
GetType方法也会调用typeof运算符,该方法对每个类型的每个对象都有效。
第一种形式:括号内代码分配资源;statement时使用资源的代码;using语句隐式产生处置资源的代码。
using(ResourceType Identifier = Expression)Statement
它会执行这些任务:
整个结构被封闭在一个隐式的块里,当finally块退出时,资源失效且不会被意外调用。
第二种形式(不推荐使用):资源在using语句前声明
using(Expression) EmbeddedStatement
        资源           使用资源
    int[][] jarArr = new int[3][]; //正确
    int[][] jarArr = new int[3][4];//编译错误
    int[][] Arr = new int[3][];//实例化顶层数组
    
    Arr[0] = new int[]{10,20,30};//实例化子数组
    Arr[1] = new int[]{40,50,60,70};
    Arr[2] = new int[]{80,90,100,110,120};
    Class MyClass{
        public int MyField = 0;
    }
    
    Class Program{
        static void Main(){
            MyClass[] mcArray = new MyClass[4];
            for(int i = 0; i < 4; i++){
                mcArray[i] = new MyClass[4];
                mcArray[i].MyField = i;
            }
            foreach(myClass item in mcArray)
                item.MyField += 10;
            foreach(MyClass item in mcArray)
                Console.WriteLine($"{item.Field}"));
        }
    }
输出为
10
11
12
13
在某些情况下,即使某个对象不是数组的基类型,也可以把它赋值给数组元素。需要符合如下条件:
因此总是可以将一个派生类的对象赋值给基类声明的数组。
简单的示例
Imports System
Public Class Rectangle
    Private length As Double
    Private width As Double
    Public Sub AcceptDetails()
        length = 4.5
        width = 3.5
    End Sub
    Public Function GetArea() As Double
        GetArea = length * width
    End Function
    Public Sub Display()
        Console.WriteLine("Length:{0}", length)
        Console.WriteLine("Width:{0}", width)
        Console.WriteLine("Area:{0}", GetArea())
    End Sub
    Shared Sub Main()
        Dim r As New Rectangle
        r.AcceptDetails()
        r.Display()
        Console.ReadLine()
    End Sub
End Class
一个简单VB程序主要的组成部分
    delegate double MyDel(int par);
    class Program
    {
        static void Main()
        {
           
            MyDel del = delegate (int x) { return x + 1; };//匿名方法
            MyDel le1 = (int x) => { return x + 1; };//Lambda表达式
            MyDel le2 = (x) => { return x + 1; };
            MyDel le3 = x=> { return x + 1; };
            MyDel le4 = x => x + 1;
            Console.WriteLine($"{del(12)}");
            Console.WriteLine($"{le1(12)}");
            Console.WriteLine($"{le2(12)}");
            Console.WriteLine($"{le3(12)}");
            Console.WriteLine($"{le4(12)}");
        }
    }
    //输出都是13
这是一种经常使用到的设计模式,sub会监听一类消息来达到pub发布的时候进行相应的处理。
看到网上有人把它和观察者模式比较

从表面上看:
观察者模式里,只有两个角色 —— 观察者 + 被观察者 而发布订阅模式里,还有一个—— 经纪人Broker
往更深层次讲:
观察者和被观察者,是松耦合的关系 发布者和订阅者,则完全不存在耦合
但是实际上,《设计模式》这本书讲观察者模式对复杂情况下如何处理进行了描述:
对于不同观察者需要关心不同的变化时需要加入ObserverManager进行复杂情况的处理。观察者将被观察时间在ObserverManager中进行注册,事件发生变更时在ObserverManager中寻找已注册该事件的观察者,将消息发送给对应的观察者。
ObserverManager就是Broker的角色,其实两个没有本质性的区别。
需要在事件中使用的代码有五部分
    class Incrementer{
        public event EventHandler CountedADozen;
              关键字  委托类型       事件名
        ……
    }
使用+=运算符添加事件处理程序,事件处理程序的规范可以是以下任意一种
    delegate void Handler();
//发布者pub
    class Incrementer
    {
        public event Handler CountedADozen;
    //public event EventHandler CountedADozen;
        public void DoCount()
        {
            for(int i = 1; i < 100; i++)
            {
                if (i % 12 == 0 && CountedADozen != null)
                    CountedADozen();
                    //CountedADozen(this,null);
            }
        }
    }
//订阅者sub
    class Dozens
    {
        public int DozensCount { get; private set; }
        public Dozens(Incrementer incrementer)
        {
            DozensCount = 0;
            incrementer.CountedADozen += IncrementDozensCount;
//void IncrementDozensCount(object source, EventArgs e)
            void IncrementDozensCount()
            {
                DozensCount++;
            }
        }
    }
    class Program
    {
        static void Main()
        {
            Incrementer incrementer = new Incrementer();
            Dozens dozensCounter = new Dozens(incrementer);
            incrementer.DoCount();
            Console.WriteLine("Number of dozens = {0}", dozensCounter.DozensCount);
        }
    }
    //输出:Number of dozens = 8
System命名空间中声明的EventHandler委托类型
首先上例中注释掉的部分,就是改为使用EventHandler委托的写法。
关于委托的声明:
    public delegate void EventHandler(object sender, EventArgs e);
这其中第二个参数是EventArgs类的对象,但是
object和EventArgs类型的参数总是基类,这样EventHandler就可以提供一个对所有事件和事件处理器都通用的签名。
对上面的实例进行修改扩展
    delegate void Handler();
    
    public class IncrementerEventArgs: EventArgs
    {
        public int IterationCount { get; set; }
    }
    class Incrementer
    {
        public event EventHandler<IncrementerEventArgs> CountedADozen;
        public void DoCount()
        {
            IncrementerEventArgs args = new IncrementerEventArgs();
            for(int i = 1; i < 100; i++)
            {
                if (i % 12 == 0 && CountedADozen != null)
                {
                    args.IterationCount = i;
                    CountedADozen(this, args);
                }
                    
            }
        }
    }
    class Dozens
    {
        public int DozensCount { get; private set; }
        public Dozens(Incrementer incrementer)
        {
            DozensCount = 0;
            incrementer.CountedADozen += IncrementDozensCount;
            void IncrementDozensCount(object source,IncrementerEventArgs e)
            {
                Console.WriteLine($"Incremented at iteration :{e.IterationCount}in {source.ToString()}");
                DozensCount++;
            }
        }
    }
    class Program
    {
        static void Main()
        {
            Incrementer incrementer = new Incrementer();
            Dozens dozensCounter = new Dozens(incrementer);
            incrementer.DoCount();
            Console.WriteLine("Number of dozens = {0}", dozensCounter.DozensCount);
        }
    }
        
}
输出:
Incremented at iteration :12in demo1.Incrementer
Incremented at iteration :24in demo1.Incrementer
Incremented at iteration :36in demo1.Incrementer
Incremented at iteration :48in demo1.Incrementer
Incremented at iteration :60in demo1.Incrementer
Incremented at iteration :72in demo1.Incrementer
Incremented at iteration :84in demo1.Incrementer
Incremented at iteration :96in demo1.Incrementer
Number of dozens = 8
Array类的Sort()方法依赖于一个叫做IComparable的接口,声明在BCL(基类库Base Class Library)中,包含唯一的方法CompareTo。
    public interface IComparable{
        int CompareTo(object obj);
    }
在IComparable接口的声明中,包含Compare方法的声明,但是没有实现,实现用分号表示。但是. NET文档中描述了该方法应该做的事情。
文档中写道,在调用该方法时,它应该返回以下几个值之一:
- 负数值:如果当前对象小于参数对象
- 正数值:如果当前对象大于参数对象
- 零 :如果两个对象相等
int类型实现了IComparable,因此sort方法可以直接排序,但是自己定义的类数组并没有实现IComparable所以直接使用sort的话会出现异常。于是可以让自己的类实现IComparable接口
    class MyClass:IComparable{
        public int TheValue;
        
        public int CompareTo(object obj){
            MyClass mc = (MyClass)obj;
            if(this.TheValue < mc.TheValue)return -1;
            if(this.TheValue > mc.TheValue)return 1;
            return 0;
        }
    }
这样就可以使用sort排序MyClass对象数组了,完整代码如下。
    class MyClass : IComparable
    {
        public int TheValue;
        public int CompareTo(object obj)
        {
            MyClass mc = (MyClass)obj;
            if (this.TheValue < mc.TheValue) return -1;
            if (this.TheValue > mc.TheValue) return 1;
            return 0;
        }
    }
    class Program
    {
        static void PrintOut(string s, MyClass[] mc)
        {
            Console.Write(s);
            foreach (var m in mc)
                Console.Write($"{m.TheValue} ");
            Console.WriteLine(" ");
        }
        static void Main()
        {
            var myInt = new[] { 20, 4, 16, 9, 2 };
            MyClass[] mcArr = new MyClass[5];
            for(int i = 0; i < 5; i++)
            {
                mcArr[i] = new MyClass();
                mcArr[i].TheValue = myInt[i];
            }
            PrintOut("Initial Order: ", mcArr);
            Array.Sort(mcArr);
            PrintOut("Sorted Order: ", mcArr);
        }
    }
    //输出:
    //Initial Order: 20 4 16 9 2
    //Sorted Order: 2 4 9 16 20
    interface IMyInterface1{
        int DoStuff(int nVar1, long lVar2);
        double DoOtherStuff(string s, long x);
    }
只有类和结构才能实现接口。如前文的Sort所示,要实现接口,类或结构必须:
接口是一个引用类型,我们可以通过类对象引用强制转换为接口类型来获取指向接口的引用。一旦有了引用,就可以使用点语法来调用接口的成员。
IIfc ifc = (IIfc1)mc;//获取接口的引用
ifc.PrintOut("interface");//使用接口的引用调用方法
ILiveBirth b = a as IliveBirth;//跟cast:(ILiveBirth)a一样
if(b!=null)
Console.WriteLine($"Baby is called:{b.BabyCalled()}");
需要强制转换当前对象的引用(this)为接口类型的引用,并使用这个指向接口的引用来调用显式接口实现。
    class MyClass: IIfc1{
        void IIfc1.PringOut(string s){
            Console.WriteLine("IIfc1");
        }
        
        public void Method1(){
            PrintOut("...");//编译错误
            this.PringOut("...");//编译错误
            
            ((IIfc1)this).PrintOut("...");//正确调用
        }
    }