CSharp学习笔记

基础

语法

base调用父类被重写的方法

public fun1(string options, string config) : base(options)
{
    this.config = config;
}

is判空

//user不为空,同时user._Eye和user.Name都不为空
if (user is {_Eye: { },Name:{}})
{
    System.Console.WriteLine("all notnull");
}
//user不为空,同时user._Eye不为空,但是user.Name为空
if (user is {_Eye: { },Name:null})
{
    System.Console.WriteLine("all notnull");
}

?作用

int? 表示可空的整形,DateTime? 表示可为空的时间。
x?y:z 表示如果表达式x为true,则返回y;
如果x为false,则返回z,是省略if{}else{}的简单形式。
a??b 当a为null时则返回b,a不为null时则返回a本身。
空合并运算符为右结合运算符,即操作时从右向左进行组合的。
如,“a??b??c”的形式按“a??(b??c)”计算。

out和ref

ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
1、out必须在函数体内初始化,在外面初始化没意义。也就是说,out型的参数在函数体内不能得到外面传进来的初始值。
2、ref必段在函数体外初始化。
3、两都在函数体的任何修改都将影响到外面。

split

var str = “,001,002,abc,def,”;
var strings = str.Split(“,”);
var strings2 = str.Split(new char[1] {‘,’}, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine(JsonSerializer.Serialize(strings));//[“”,“001”,“002”,“abc”,“def”,“”,“”]
Console.WriteLine(JsonSerializer.Serialize(strings2));//[“001”,“002”,“abc”,“def”]

技巧

扩展方法Extension

  1. 需要写在一个静态类中
  2. 必须是一个静态方法
  3. 通过第一个参数和this关键字指定扩展的目标类型
  4. 不同类型的扩展方法不一定要写在同一个类中
public static class Utility
{
   public static bool IsNotNullOrEmpty<T>(this IEnumerable<T> value)
   {
      if (value != null)
      {
         return value.Count() > 0;
      }
      return false;
   }

   public static bool IsNullOrEmpty<T>(this IEnumerable<T> value)
   {
      return !value.IsNotNullOrEmpty();
   }
}

特性Attribute

类似于Java注解

class MyAttribute : Attribute
{
    private string name;
    public MyAttribute(string name)
    {
        this.name = name;
    }
    public void GetName()
    {
        Console.WriteLine($"my name is {name}");
    }
}[My("charles")]
public void ShowAttribute()
{
    Console.WriteLine("I have a Attribute");
}

扫描类和特性

var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
    var types = assembly.GetTypes();
    foreach (var type in types)
    {
        var attribute = type.GetCustomAttribute<ReqAuthorizeAttribute>();
    }
}

委托/回调

public class DataTableUtil
{
    public delegate void OnReadRow(DataRow row, DataColumnCollection columns);
    public static void ParseEntity(DataTable dataTable,OnReadRow listener)
    {
        var columns = dataTable.Columns;
        foreach (DataRow row in dataTable.Rows)
        {
            listener(row,columns);
        }
    }
}
class Program
{
    static void Main(string[] args)
    {
        DataTableUtil.ParseEntity(dataTable, (dataRow, columns) => { });
    }
}

Rider

修改项目运行环境
项目右键->Properties->Web->Open application.config in the editor

多线程

主线程退出后,子线程也会结束。

单个线程

class Program
{
    private static void CountNum()
    {
        for (int i = 0; i < 100; i++)
        {
            Count++;
            Console.WriteLine("子线程>"+Count);
        }
    }
    private static int Count = 0;
    static void Main(string[] args)
    {
        /*var thread = new Thread(() =>
        {
            for (int i = 0; i < 100; i++)
            {
                Count++;
                Console.WriteLine("子线程>"+Count);
            }
        });*/
        // var thread = new Thread(CountNum);
        // thread.Start();
        Task.Run(new Action(CountNum));
        for (int i = 0; i < 100; i++)
        {
            Count++;
            Console.WriteLine("主线程:"+Count);
        }
        Thread.Sleep(2000);
    }
}

线程池

class Program
{
    static void Main(string[] args)
    {
        //SetMaxThreads单独设置时,线程最大值是不能小于计算机逻辑处理器个数(任务管理器->性能->CPU->逻辑处理器)
        //SetMinThreads和SetMaxThreads配合使用,最大最小线程数量生效
        //workerThreads:工作者线程是主要用作管理CLR内部对象的运作,通常用于计算密集的任务。
        //completionPortThreads:I/O(Input/Output)线程主要用于与外部系统交互信息,如输入输出。
        ThreadPool.SetMinThreads(5, 5);
        ThreadPool.SetMaxThreads(10, 10);
        for (int i = 0; i < 40; i++)
        {
            // ThreadPool.QueueUserWorkItem(new WaitCallback(TaskSimple));
            // ThreadPool.QueueUserWorkItem(TaskSimple);
            // ThreadPool.QueueUserWorkItem(state => { });
            ThreadPool.QueueUserWorkItem(TaskSimple,i);
        }

        Thread.Sleep(6000);
    }

    private static void TaskSimple(object stateInfo)
    {
        Thread.Sleep(200);
        // CompletedWorkItemCount  获取迄今为止已处理的工作项数。
        // PendingWorkItemCount    获取当前已加入处理队列的工作项数。
        // ThreadCount 获取当前存在的线程池线程数。
        Console.WriteLine("线程ID:"+Thread.CurrentThread.ManagedThreadId+"      Info:"+stateInfo+
                          "\r\n排队:"+ThreadPool.PendingWorkItemCount+",完成:"+ThreadPool.CompletedWorkItemCount+",线程数量:"+ThreadPool.ThreadCount);
    }
}

定时器

System.Threading
var timer = new Timer(state =>
{
    Console.WriteLine("线程ID:"+Environment.CurrentManagedThreadId+
                      ",线程池:"+Thread.CurrentThread.IsThreadPoolThread+
                      ",时间:"+DateTime.Now);
},"",0,0);//dueTime第一次执行时间(0立即执行),period循环执行时间(0不循环),单位毫秒
// timer.Dispose();//释放定时器
System.Timers
var timer = new Timer(2000);//创建定时器,设置间隔时间1000毫秒
timer.Elapsed += (sender, eventArgs) =>
{
    Console.WriteLine("线程ID:" + Environment.CurrentManagedThreadId +
                      ",线程池:" + Thread.CurrentThread.IsThreadPoolThread +
                      ",时间:" + DateTime.Now);
};
timer.AutoReset = true;//设置是执行一次(false)还是一直执行(true)
timer.Enabled = true;//或者timer2.Start(); 都可以启动
//timer2.Stop();timer2.Enabled = false;都可以停止

新特性

C# 1

Classes:面向对象特性,支持类类型
Structs:结构
Interfaces:接口
Events:事件
Properties:属性,类的成员,提供访问字段的灵活方法
Delegates:委托,一种引用类型,表示对具有特定参数列表和返回类型的方法的引用
Expressions,Statements,Operators:表达式、语句、操作符
Attributes:特性,为程序代码添加元数据或声明性信息,运行时,通过反射可以访问特性信息
Literals:字面值(或理解为常量值),区别常量,常量是和变量相对的

C# 2特性 (VS 2005)

Generics:泛型
Partial types:分部类型,可以将类、结构、接口等类型定义拆分到多个文件中
Anonymous methods:匿名方法 Iterators:迭代器
Nullable types:可以为Null的类型,该类可以是其它值或者null
Getter/setter separate accessibility:属性访问控制
Method group conversions (delegates):方法组转换,可以将声明委托代表一组方法,隐式调用
Co- and Contra-variance for delegates and interfaces:委托、接口的协变和逆变
Static classes:静态类
Delegate inference:委托推断,允许将方法名直接赋给委托变量

C# 3特性 (VS 2008)

Implicitly typed local variables: Object and collection
initializers:对象和集合初始化器
Auto-Implemented properties:自动属性,自动生成属性方法,声明更简洁
Anonymous types:匿名类型
Extension methods:扩展方法
Query expressions:查询表达式
Lambda expression:Lambda表达式
Expression trees:表达式树,以树形数据结构表示代码,是一种新数据类型
Partial methods:部分方法

C# 4特性 (VS 2010)

Dynamic binding:动态绑定
Named and optional arguments:命名参数和可选参数
Generic co- and contravariance:泛型的协变和逆变
Embedded interop types (“NoPIA”):开启嵌入类型信息,增加引用COM组件程序的中立性

C# 5特性 (VS 2012)

Asynchronous methods:异步方法
Caller info attributes:调用方信息特性,调用时访问调用者的信息

C# 6特征 (VS 2015)

Compiler-as-a-service (Roslyn)
Import of static type members into namespace:支持仅导入类中的静态成员
Exception filters:异常过滤器
Await in catch/finally blocks:支持在catch/finally语句块使用await语句
Auto property initializers:自动属性初始化
Default values for getter-only properties:设置只读属性的默认值
Expression-bodied members:支持以表达式为主体的成员方法和只读属性
Null propagator (null-conditional operator, succinct null checking):Null条件操作符
String interpolation:字符串插值,产生特定格式字符串的新方法
nameof operator:nameof操作符,返回方法、属性、变量的名称
Dictionary initializer:字典初始化

C# 7 特征 (Visual Studio 2017)

Out variables:out变量直接声明,例如可以out in parameter Pattern
matching:模式匹配,根据对象类型或者其它属性实现方法派发
Tuples:元组
Deconstruction:元组解析
Discards:没有命名的变量,只是占位,后面代码不需要使用其值
Local Functions:局部函数 Binary
Literals:二进制字面量
Digit Separators:数字分隔符
Ref returns and locals:引用返回值和局部变量
Generalized async return types:async中使用泛型返回类型 More
expression-bodied members:允许构造器、解析器、属性可以使用表达式作为body
Throw expressions:Throw可以在表达式中使用

C# 7.1 特征 (Visual Studio 2017 version 15.3)

Async main:在main方法用async方式
Default expressions:引入新的字面值default
Reference assemblies:
Inferred tuple element names:
Pattern-matching with generics:

C# 7补充

out 变量(out variables)

以前我们使用out变量必须在使用前进行声明,C# 7.0 给我们提供了一种更简洁的语法 “使用时进行内联声明”。

var input = ReadLine();
if (int.TryParse(input, out var result))
{
    WriteLine("您输入的数字是:{0}",result);
}
else
{
    WriteLine("无法解析输入...");
}

元组(Tuples)

//创建一个元组
var tuple = (1, 2);                           // 使用语法糖创建元组
var tuple2 = ValueTuple.Create(1, 2);         // 使用静态方法【Create】创建元组
var tuple3 = new ValueTuple<int, int>(1, 2);  // 使用 new 运算符创建元组
WriteLine($"first:{tuple.Item1}, second:{tuple.Item2}, 上面三种方式都是等价的。");

// 左边指定字段名称
(int one, int two) tuple = (1, 2);
WriteLine($"first:{tuple.one}, second:{tuple.two}");

// 右边指定字段名称
var tuple2 = (one: 1, two: 2);
WriteLine($"first:{tuple2.one}, second:{tuple2.two}");

// 左右两边同时指定字段名称
(int one, int two) tuple3 = (first: 1, second: 2);    /* 此处会有警告:由于目标类型(xx)已指定了其它名称,因为忽略元组名称xxx */
WriteLine($"first:{tuple3.one}, second:{tuple3.two}");

//解构元组
var (one, two) = GetTuple();

WriteLine($"first:{one}, second:{two}");
static (int, int) GetTuple()
{
    return (1, 2);
}

var (Name, Age) = new Student("Mike", 30);
WriteLine($"name:{Name}, age:{Age}");

模式匹配(Pattern matching)

is表达式(is expressions)

条件控制语句(obj is type variable)
{
   // Processing...
}

switch语句更新(switch statement updates)

static int GetSum(IEnumerable<object> values)
{
    var sum = 0;
    if (values == null) return 0;

    foreach (var item in values)
    {
        switch (item)
        {
            case 0:                // 常量模式匹配
                break;
            case short sval:       // 类型模式匹配
                sum += sval;
                break;
            case int ival:
                sum += ival;
                break;
            case string str when int.TryParse(str, out var result):   // 类型模式匹配 + 条件表达式
                sum += result;
                break;
            case IEnumerable<object> subList when subList.Any():
                sum += GetSum(subList);
                break;
            default:
                throw new InvalidOperationException("未知的类型");
        }
    }

    return sum;
}

使用方法:

switch (item)
{
    case type variable1:
        // processing...
        break;
    case type variable2 when predicate:
        // processing...
        break;
    default:
        // processing...
        break;
}

原理解析:此 switch 非彼 switch,编译后你会发现扩展的 switch 就是 as 、if 、goto 语句的组合体。同 is expressions 一样,以前我们也能实
现只是写法比较繁琐并且可读性不强。
总结:模式匹配语法是想让我们在简单的情况下实现类似与多态一样的动态调用,即在运行时确定成员类型和调用具体的实现。

局部函数(Local functions)

static IEnumerable<char> GetCharList(string str)
{
    if (IsNullOrWhiteSpace(str))
        throw new ArgumentNullException(nameof(str));

    return GetList();

    IEnumerable<char> GetList()
    {
        for (int i = 0; i < str.Length; i++)
        {
            yield return str[i];
        }
    }
}

使用方法:

[数据类型,void] 方法名([参数]{
   // Method body;[] 里面都是可选项
}

C# 8(VS 2019 , .NET Core 3.0)

switch 表达式

// 简单演示一下 switch 表达式的使用
        public static void Sample1()
        {
            string SwitchExpression(string input) =>
                input switch
                {
                    "1" => "111111",
                    "2" => "222222",
                    _ => "000000" // 这里的“_”相当于 switch 语句中的 default
                };

            Console.WriteLine(SwitchExpression("1")); // 111111
            Console.WriteLine(SwitchExpression("2")); // 222222
            Console.WriteLine(SwitchExpression("1234")); // 000000
        }

默认接口方法

接口是用来约束行为的,在 C# 8 以前,接口中只能进行方法的定义

public interface IUser
{
    string GetName() =>  "oec2003";
}

using 变量声明

我们都知道 using 关键字可以导入命名空间,也能定义别名,还能定义一个范围,在范围结束时销毁对象,在 C# 8.0 中的 using 变量声明可以让代码看起来更优雅。

static void Main(string[] args)
{
    var connString = "Host=221.234.36.41;Username=gpadmin;Password=123456;Database=postgres;Port=54320";
    using var conn = new NpgsqlConnection(connString);
    conn.Open();
 
    using var cmd = new NpgsqlCommand("select * from user_test", conn);
    using var reader = cmd.ExecuteReader();
 
    while (reader.Read())
       Console.WriteLine(reader["user_name"]);
     Console.ReadKey();
}

Null 合并赋值

这是一个很有用的语法糖,在 C# 中如果调用一个为 Null 的引用类型上的方法,会出现经典的错误:”未将对应引用到对象的实例“,所以我们在返回引用类型时,需要做些判断

static void Main(string[] args)
{
    List<string> list = GetUserNames();
    list ??= new List<string>();
    Console.WriteLine(list.Count);
}

C# 9

init修饰符

init 是属性的一种修饰符,可以设置属性为只读,但在初始化的时候却可以指定值

public class UserInfo
{
    public string Name { get; init; }
}
UserInfo user = new UserInfo { Name = "oec2003" };
//当 user 初始化完了之后就不能再改变 Name 的值
user.Name = "oec2004";

record

在 C# 9 中新增了 record 修饰符,record 是一种引用类型的修饰符,使用 record 修饰的类型是一种特别的 class,一种不可变的引用类型。(重写了 = =、Equals 等 ,是按照属性值的方式来进行比较的)

public record UserInfo
{
    public string Name { get; set; }
}
static void Main(string[] args)
{
    UserInfo user1 = new UserInfo { Name = "oec2003" };
    UserInfo user2 = new UserInfo { Name = "oec2003" };
    Console.WriteLine(user1== user2); //True
}

如果想要不影响原对象实例,就需要使用到深拷贝,在 record 中,可以使用 with 语法简单地达到目的

public record UserInfo
{
    public string Name { get; set; }
}
static void Main(string[] args)
{
    UserInfo user = new UserInfo { Name = "oec2003" };
    UserInfo user1 = user with { Name="eoc2004"};
 
    Console.WriteLine(user.Name); // oec2003
    Console.WriteLine(user1.Name); // oec2004
}

模式匹配增强

模式匹配中我觉得最有用的就是对 Null 类型的判断

public static string GetUserName(UserInfo user)
{
    if(user is not null)
    {
        return user.Name;
    }
    return string.Empty;
}

顶级语句

将Class的定义和主函数Main的声明省略掉,只写出你的核心业务代码,就成了顶级语句。
这个不知道有啥用?但挺好玩的,创建一个控制台程序,将 Program.cs 中的内容替换为下面这一行,程序也能正常运行:(比如core6 webapi的Program.cs)

System.Console.WriteLine("Hello World!");

C# 10

全局using指令

使用关键字 global 定义整个项目的命名空间导入。推荐做法是,将全局导入放在一个单独的文件中(每个项目一个),可以命名为 usings.cs 或imports.cs。其中的内容大致为:

global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.HttpsPolicy;
global using Microsoft.AspNetCore.Identity;
global using Microsoft.AspNetCore.Identity.UI;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading.Tasks;

命名空间声明

类似于Java的package
可使用 声明的新形式,声明所有后续声明都是已声明的命名空间的成员:

namespace MyNamespace;

这个新语法为 namespace 声明节省了水平和垂直空间。

空参数检查

本着减少样板代码的精神,C# 提供了一个非常好的新功能:空参数检查。你肯定编写过需要检查空值的方法。比如,如下代码:

public UpdateAddress(int personId, Address newAddress){
    if (newAddress == null)
    {
    throw new ArgumentNullException("newAddress");
    }   
}

如今,你只需要在参数名称末尾添加“!!”,C#就会自动加入这种空参数检查。上述代码可以简化为:

public UpdateAddress(int personId, Address newAddress!!){    ...}

现在,如果传递一个空值给 Address,就会自动抛出 ArgumentNullException。

required 属性

以前,我们只能通过类构造函数来确保正确地创建对象。如今,我们经常使用更加轻量级的结构,比如下面这个记录中自动实现的属性:

public record Employee{
    public string Name { get; init; }
    public decimal YearlySalary { get; init; }
    public DateTime HiredDate{ get; init; }
}

在创建这类轻量级对象的实例时,我们可能会使用对象的初始化语法:

var theNewGuy = new Employee{
    Name = "Dave Bowman",
    YearlySalary = 100000m,
    HiredDate = DateTime.Now()};

但是,如果你的对象中的某些属性是必须的,该怎么办?你可以像以前一样,添加一个构造函数,但如此一来就需要添加更多的样板代码了。此外,将值从一个参数复制到属性也是另一个很容易理解但很常见的错误。
C# 10 引入的关键字 required 可以消灭这类问题:

public record Employee{
    public required string Name { get; init; }
    public decimal YearlySalary { get; init; }
    public DateTime HiredDate{ get; init; }}

如此一来,如果不设置 Name 属性就无法创建 Employee 了。

关键字field

多年来,为了通过自动实现属性简化代码,C# 团队做出了大量努力,上面的 Employee 记录就是一个很好的例子,它使用 get 和 init 关键字声明了三个不可变的属性。数据存储在三个私有字段中,但这些字段都是自动创建的,无需人工干预。而且你永远不会看到这些字段。
自动实现的属性很棒,但它们的作用也仅限于此。当无法使用自动实现的属性时,你就必须添加支持字段到类,并编写正常的属性方法,就像回到 C# 2一样。但是 C# 10中提供了一个关键字field,可以自动创建支持字段。
例如,假设你想创建一个记录,用于处理初始属性值。在下面的代码中,我们对 Employee 类进行了一些修改,确保HiredDate 字段只包含来自 DateTime 对象的日期信息(不包含时间信息):

public record Employee{
    public required string Name { get; init; }
    public decimal YearlySalary { get; init; }
    public DateTime HiredDate{ get; init => field = value.Date(); }}

这段代码非常整洁、简单,而且很接近声明式。
你可以使用关键字 field 访问 get、set 或 init 中的字段。而且,你可能需要验证某个属性,就像验证普通类中的属性一样:

private string _firstName;
public string FirstName{
    get    {
        return _firstName;
    }
    set    {
        if (value.Trim() == "")
            throw new ArgumentException("No blank strings");
         _firstName = value;
    }
}

你可以使用 field 来验证自动实现的属性:

public string FirstName {get;
    set    {
        if (value.Trim() == "")
            throw new ArgumentException("No blank strings");
        field = value;
    }
}

本质上,只要不需要修改属性的数据类型,就不需要自行声明支持字段。

C# 11

泛型属性

可以声明基类为 的泛型类。 这为需要 System.Type 参数的属性提供了更方便的语法。 以前需要创建一个属性,该属性将 Type 作为其构造函数参数:

// Before C# 11:
public class TypeAttribute : Attribute
{
   public TypeAttribute(Type t) => ParamType = t;
   public Type ParamType { get; }
}

并且为了应用该属性,需要使用 typeof 运算符:

[TypeAttribute(typeof(string))]
public string Method() => default;

使用此新功能,可以改为创建泛型属性:

// C# 11 feature:
public class GenericAttribute<T> : Attribute { }

然后指定类型参数以使用该属性:

[GenericAttribute<string>()]
public string Method() => default;

应用属性时,必须提供所有类型参数。 换句话说,泛型类型必须完全构造。

原始字符串文本

原始字符串文本 是字符串文本的新格式。 原始字符串文本可以包含任意文本,包括空格、新行、嵌入引号和其他特殊字符,而无需转义序列。 原始字符串文本以至少三个双引号开头, (“”“) 个字符。 它以相同数量的双引号字符结尾。 通常,原始字符串文本在单个行上使用三个双引号来启动字符串,另一行的三个双引号结束字符串。 尾引号和尾引号前面的换行符不包括在最终内容中:

string longMessage = """
    This is a long message.
    It has several lines.
        Some are indented
                more than others.
    Some should start at the first column.
    Some have "quoted text" in them.
    """;

右双引号左侧的任何空格都将从字符串文本中删除。 原始字符串文本可以与字符串内插结合使用,以在输出文本中包含大括号。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值