C#基础语法

C#

编程规范

命名规则

  1. 编程符必须以字母或下划线开头
  2. 标识符可以包含Unicode字符、十进制数字字符、Unicode连接字符、Unicode组合字符或Unicode格式字符

可以在标识符上使用@前缀声明与C#关键字匹配的标识符。eg.@if声明为if的标识符

命名约定

类型名称、命名空间和所有公共成员使用PascalCase

  • 接口名称以I开头,属性类型以Attribute结尾,对变量、方法和类使用有意义的描述名称
  • 枚举类型对非标记使用单数名词,对标记使用复数名词,标识符不应包含两个连续的下划线_字符,这些名称保留給编译器生成
  • 清晰>简洁,类名和方法名称采用PascalCase,常量名包括字段和局部变量也是
  • 对方法参数、局部变量使用驼峰式大小写,专用实例字段以下划线_开头,其余文本为驼峰式大小写,静态字段以s_开头
  • 避免在名称中使用缩写或首字母,广为人知的除外,使用遵循反向域名表示法的命名空间
  • S用于结构,C用于类,M用于方法,v用于变量,p用于参数,r用于ref参数

编码约定

字符串

  1. 使用内插连接短字符串
string displayName = $"{first}, {second}";
  1. 循环追加字符串,尤其是大量文本,使用stringbuilder
var a = "aaaaaaaaaaaaaaaaaaa";
var sb = new StringBuilder();
foreach (var i in a)
{
    sb.Append(i);
}

数组

  1. 声明行上初始化数组时,使用简介的语法
// 不能使用var
int[] v1 = {1, 2, 3, 4, 5};
// 显式
var v2 = new string[]{"a, b, c, d"};

委托

使用Func<>和Action<>,而不是委托在类中定义委托方法

Action<string> a1 = x => Console.WriteLine($"x is:{x}");
Action<string, string> a2 = (x, y) => Console.WriteLine($"x is:{x}, y is {y}");

Func<string, int> f1 = x => Convert.ToInt32(x);
Func<int, int, int> f2 = (x, y) => x + y;

// 使用Func<> 或 Action<>委托定义的签名调用方法
a1("string for x");
a2("string for x", "string for y");
Console.WriteLine($"The value is {f1("1")}");
Console.WriteLine($"The sum is {f2(1, 2)}");

多播委托

// 委托是一种声明,类似于一个接口,带有关键字,后面跟着返回类型和参数列表
public delegate void MyDelegate(string message);

public class Program
{
    public static void Main()
    {
        MyDelegate printDelegate = new MyDelegate(PrintMessage);
        printDelegate += PrintUpperCase;
        printDelegate("Hello World");
    }
    public static void PrintMessage(string message)
    {
        Console.WriteLine(message);
    }
    public static void PrintUpperCase(string message)
    {
        Console.WriteLine(message.ToUpper());
    }
}

释放资源

// 显示释放资源
Font bodyStyle = new Font("Arial", 10.0f);
try
{
    byte charset = bodyStyle.GdiCharSet;
}
finally
{
    if (bodyStyle != null)
    {
        ((IDisposable)bodyStyle).Dispose();
    }
}
// 使用using,在离开作用域时自动调用Dispose方法,即使出现异常任然可以释放掉资源
using Font normalStyle = new Font("Arial", 10.0f);
byte charset3 = normalStyle.GdiCharSet;

new关键字

// 创建对象时下列两种方法等效
var user = new User();
User user2 = new();

索引

^类似于取反,..类似于python中的:,可以切片。StringSpanReadOnlySpanList 支持索引,但不支持范围。

在数组中获取范围是从初始数组复制的数组而不是引用的数组,修改生成的值不会更改数组中的值

string[] words = [
    // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
];
// brown
Console.WriteLine(words[^1]);
// uick
Console.WriteLine(words[1][1..]);
// ui
Console.WriteLine(words[1][1..3]);
// brown
Console.WriteLine(words[words.Length - 1]);

Base

类比理解为Java中的extend和implement,具有几个不同的用途,都与继承有关

// 调用基类构造函数
public class DerivedClass: BaseClass{
    public DerivedClass(): base(){}		// 调用基类构造函数
    public DerivedClass(): base(value){}	// 调用且传参
}

// 访问基类成员
public class BaseClass{
    public void PrintMessage() {
        CW("...");
    }
}

public class Derived: BaseClass{
    public void PrintMessage() {
        CW("...");
        base.PrintMessage();	// 调用基类PrintMessage方法
    }
}

// 指定基类作为强制转换目标
public class BaseClass{}

public class Derived: BaseClass{
    public void Method() {
        BaseClass bc = base;	// 将派生类对象转换为基类类型
    }
}

?.条件运算符

类似Java里面的optional,左侧为null则表达式为null,不为null则走后续流程,避免空指针

if pwd == auth?.pwd

??合并运算符

类似Python的海象符,如果左边为null则返回右边的数

string result = str ?? "default";

Using()

Using是资源管理语句,常用于需要显式释放的非托管资源,如文件流、网络连接、数据库连接等。

  • using 块内的代码执行完毕时,无论是正常完成还是因为异常而退出,using 语句都会自动调用每个对象的 Dispose 方法。这样可以确保释放
// base64编码的字符串转换成字节数组
using(var mStream = new MemoryStream(Convert.FromBase64String(source)));

// 使用解密器对数据解密
using (var cryptoStream = new CryptoStream(mStream,
                       DesHandler.CreateDecryptor(DesHandler.Key, DesHandler.IV), CryptoStreamMode.Read));

// 创建对象读取解密后的文本数据
using (var reader = new StreamReader(cryptoStream));

readonly

类比Java中的finall,初始化后不能修改

  • 不能用于修饰静态字段、修饰类或接口
  • 字段或局部变量使用使用了readonly则不需要const
// 字段,必须在声明时或构造函数中初始化,不能修改
public readonly int Field;

// 变量,必须在声明时初始化,不能修改
public void Func() {
    readonly int local = 10;
}

// 方法,不能包含任何可变参数,不可抛出异常
public readonly void MyMethod() {
    // pass
}

// 委托,不能更改
public readonly Action MyDelegate;

// 属性,get访问器必须返回一个常量,而set访问器不能被实现
public readonly int Property{get; private set;}

out关键字

用于声明的一个输出参数,方法调用时通过参数通过引用传递,如果使用out参数则必须在方法体内为每个out参数赋值。我的理解是,就是将带有out关键字的参数return出去,

public void Example(int a, out int b) {
    b = a * 2;
}
int result;
Example(5, out result);	// result = 10

匿名类型

将只读数据封装到单个对象中,而无需显示定义一个类型

var v = new {Amount = 108, Message = 'Hello'};

通常用在查询表达式的select子句中(LINQ),其用来初始化的属性不能为null、匿名函数或指针类型。匿名类型是class类型,直接派生自object,且无法强制转换为除object外的任何类型,支持采用with表达式形式的非破坏性修改,类似于Java中的Set不过只能修改已存在的属性。匿名类型会重写ToString方法

var apple = new {Item = "apple", Price = 1.35};
var onSale = apple with {Price=0.79};
Console.WriteLine(apple);
Console.WriteLine(onSale);

record 记录关键字

类似Java中的final,默认实现不可变性(不可更改对象的任何属性或字段值)、Equals和GetHashCode方法及ToString方法,自动提供了构造函数、属性的比较和字符串表示的功能

  • 值相等性:record 类型自动拥有一个经过优化的 Equals 方法,该方法比较两个 record 实例的字段值是否相等。
  • 简洁的声明式语法:record 允许使用更简洁的语法来声明只包含数据的类。
  • 不可变性:record 类型的实例一旦创建,其状态就不能更改(除非显式地使用可变记录)。
  • 继承:record 可以继承自其他 record 或 class,并且可以添加额外的成员。

以下情况考虑使用记录:

  1. 定义依赖值相等性的数据模型
  2. 定义对象不可变类型

关系模式

string WaterState(int temp) => temp switch
    {
        (>32) and (<212) => "liquid",
        < 32 => "solid",
        > 212 => "gas",
        32 => "solid/liquid transition",
        212 => "liquid/gas transition",
        _ => throw new ArgumentOutOfRangeException()
    };

判空

str

string.IsNullOrEmpty(s);	// 如果是空串判不出来
string.IsNullOrWhiteSpace(s);	// 空串判的出来

List

if (myList == null || !myList.Any()){}	// 集合为null或没有元素
if (myList?.Any() == false){}	// 集合为null或没有元素

对象

if (obj == null) {}	// 对象为空
if (obj is null) {} // 对象为空

Object.ReferenceEquals(obj1, obj2);	// 判断对象引用地址是否一样
object.Equals(str1, str2);	// 判断两对象值是否相等,需要重写Equals方法
object DefaultObj = obj ?? new object();	// 使用??运算符提供默认值

值引用

int? myInt = null;
if (!myInt.HasValue) {}
int defaultInt = myInt ?? 0;	// 使用??运算符提供默认值

主构造函数

  1. 初始化属性
// 只读情况下
public readonly struct Distance(double dx, double dy)
{
    public readonly double Magnitude { get; } = Math.Sqrt(dx * dx + dy * dy);
    public readonly double Direction { get; } = Math.Atan2(dy, dx);
}

// 两种方法初始化对象效果一致
public readonly struct Distance
{
    public readonly double Magnitude { get; }

    public readonly double Direction { get; }

    public Distance(double dx, double dy)
    {
        Magnitude = Math.Sqrt(dx * dx + dy * dy);
        Direction = Math.Atan2(dy, dx);
    }
}

// 非只读情况下,
public struct Distance(double dx, double dy)
{
    public readonly double Magnitude => Math.Sqrt(dx * dx + dy * dy);
    public readonly double Direction => Math.Atan2(dy, dx);

    public void Translate(double deltaX, double deltaY)
    {
        dx += deltaX;
        dy += deltaY;
    }

    public Distance() : this(0,0) { }
}

LINQ

Where

从数据源中筛选出元素

from city in cities where city.Pop is < 200 and >100 select city;

排序

OrderBy

可按升序或降序排列,例子中以Area为主,population为辅

var orderedEnumerable = from country in countries 
    orderby country.Area, country.Population descending select country;

ThenBy

按升序执行次要排序

Reverse

反转集合中的元素

Join

将数据源中元素于另一个数据源元素进行关联和/或合并,连接序列之后必须使用select或group语句指定存储在输入序列中的元素。示例关联Category属性与categories字符串数组中一个类别匹配的prod对象

var cateQuery = from cat in categories
            join prod in products on cat equals prod.Category
            select new
            {
                Category = cat,
                Name = prod.Name
            };

Let

使用let将结果存储在新范围变量中

from name in names select names 
    let firstName = name.Split(" ")[0]
    select firstName;

多查询

var query = from student in students
            // 按照student.Year分组
            group student by student.Year
            // 为分组定义别名,后续使用别名进行分组
            into studentGroup
            select new
            {   // 每个分组的键,学生的年级
                Level = studentGroup.Key,
                // 每个分组中,所有学生的平均成绩
                HighestScore = (from student2 in studentGroup select student2.ExamScores.Average()).Max()
            };

查询对象

var entity = from o in InComingOrders
    where o.OrderSize > 5
    select new Customer { Name = o.Name, Phone = o.Phone };
// LINQ写法
var entity2 = InComingOrders.Where(e => e.OrderSize > 5)
    .Select(e => new Customer { Name = e.Name, Phone = e.Phone });

作为数据表达式(Lambda)

结合out关键字返回查询

void QueryMethod(int[] ints, out List<string> returnQ) =>
            returnQ = (from i in ints where i < 4 select i.ToString()).ToList();

int[] nums = [0, 1, 2, 3, 4, 5, 6, 7];
QueryMethod(nums, out List<string> result);
foreach (var item in result)
{
    Console.WriteLine(item);
}

eg

// 普通方法编写查询总分数据
var studentQuery1 = from student in studnets
    let totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3]
    select totalScore;
// 使用Linq方法编写查询总分数据
var studentQuery2 = studnets.Select(e => e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3]);
// 统计平均分
double average = studentQuery1.Average();

// 将大于平均分的学生数据映射为对象
var query1 =
    from student in studnets
    let x = student.Scores[0] + student.Scores[1] +
            student.Scores[2] + student.Scores[3]
    where x > average
    select new { id = student.ID, score = x };
// 使用Linq写法
var query2 = studnets.Where(e => e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] > average).Select(e =>
    new { id = e.ID, score = e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] });
// Linq简洁写法
var query3 = studnets.Select(e => new { id = e.ID, score = e.Scores[0] + e.Scores[1] + e.Scores[2] + e.Scores[3] })
    .Where(e => e.score > average);


foreach (var item in query1)
{
    Console.WriteLine("Student ID: {0},Score: {1}", item.id, item.score);
}

投影运算

SelectMany

多个from子句投影字符串列表中每个字符串中的单词

List<string> phrases = ["an apple a day", "the quick brown fox"];
// 普通写法
var query = from phrase in phrases from word in phrase.Split(' ') select word;
// Linq写法
var query2 = phrases.SelectMany(e => e.Split(' '));

Zip列表压缩元组,类似python

// An int array with 7 elements.
IEnumerable<int> numbers = [1, 2, 3, 4, 5, 6, 7];
// A char array with 6 elements.
IEnumerable<char> letters = ['A', 'B', 'C', 'D', 'E', 'F'];


foreach (var (first, second, third) in numbers.Zip(letters, emoji))
{
    Console.WriteLine($"Number:{first} is zipped with letter: {second} and emoji {third}");
}

Set集合操作

去重

string[] words = ["the", "quick", "brown", "fox", "jumped", "over", "the", "lazy", "dog"];
// 去重
var query = from word in words.Distinct() select word;
// 根据条件去重
var query2 = from word in words.DistinctBy(e => e.Length)select word;

差集

string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// console:queik、brown、fox,输出1在2中没有的元素
IEnumerable<string> query = from word in words1.Except(words2) select word;
// expectBy同理,根据自定义字段进行操作
var result = new List<Person> { new Person { Name = "Alice" }, new Person { Name = "Bob" } }
                            .ExceptBy(person => person.Name,
                                     new List<Person> { new Person { Name = "Alice" }, new Person { Name = "Charlie" } });
// result 将包含 { new Person { Name = "Bob" } },因为 "Alice" 在两个集合中都存在,而 "Bob" 和 "Charlie" 只在第一个集合中。

交集

string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// 输出the
IEnumerable<string> query = from word in words1.Intersect(words2) select word;
var list = words1.Intersect(words2).Select(e => e.ToUpper()).ToList();
// 通过比较名称生成 Teacher 和 Student 的交集
(Student person in
    students.IntersectBy(
        teachers.Select(t => (t.First, t.Last)), s => (s.FirstName, s.LastName)))

并集

string[] words1 = ["the", "quick", "brown", "fox"];
string[] words2 = ["jumped", "over", "the", "lazy", "dog"];
// 使用UnionBy
(var person in
    students.Select(s => (s.FirstName, s.LastName)).UnionBy(
        teachers.Select(t => (FirstName: t.First, LastName: t.Last)), s => (s.FirstName, s.LastName)))
// 输出:the quick brown fox jumped over lazy dog
var query = (from word in words1.Union(words2) select word).ToList();
var list = words1.Union(words2).ToList();

限定符

  • All():所有
  • Any():任何
  • Contains():正好
IEnumerable<string> names = from student in students
                            where student.Scores.Contains(95)
                            select $"{student.FirstName} {student.LastName}: {string.Join(", ", student.Scores.Select(s => s.ToString()))}";
  • Skip():跳过序列中指定位置之前的元素
  • SkipWhile():基于谓词函数跳过元素,直到元素不符合条件
  • Take():获取序列中指定位置之前的元素
  • TakeWhile():同上操作
  • Chunk():将序列元素拆分为指定最大大小的区块
var resource = Enumerable.Range(0, 8);
// 012
foreach (var i in resource.Take(3)){ }
// 345678
foreach (var i in resource.Skip(3)){ }
// 012345
foreach (var i in resource.TakeWhile(e=>e<5)){ }
// 678
foreach (var i in resource.SkipWhile(e=>e<5)){ }
// 平均分块,将数据分成三块,123、456、78
int chunkNum = 1;
foreach (int[] chunk in Enumerable.Range(0, 8).Chunk(3))
{
    Console.WriteLine($"Chunk {chunkNum++}:)");
    foreach (int item in chunk)
    {
        Console.WriteLine($"   {item}");
    }
    Console.WriteLine();
}

数据类型转换

  1. AsEnumerable - 返回类型转化为IEnumerable的输入
  2. AsQueryable - 泛型IEnumerable转换为泛型IQueryable
  3. Cast - 集合中的元素转换为指定类型
  4. OfType - 转换为指定类型的能力筛选值
  5. ToArray - 集合转换为数组(强制执行查询)
  6. ToDictionary - 根据键选择器函数将元素放入 Dictionary。 此方法强制执行查询
  7. ToList - 集合转换为List
  8. ToLookUp - 根据键选择器函数将元素放入 Lookup(一对多字典,强制执行查询

连表

  1. Join - 根据键选择函数Join两个序列并提取对 - join...in...on...equals
  2. GroupJoin - 根据键选择器函数Join两个序列,并对每个元素的结果匹配项分组 - join...in...on...equals...into...

单键

Teacher和Department匹配,TeacherId与该Teacher相匹配

var query = from department in departments
            join teacher in teachers on department.TeacherID equals teacher.ID
            select new
            {
                DepartmentName = department.Name,
                TeacherName = $"{teacher.First} {teacher.Last}"
            };
// Linq
var query = teachers
    // 主表连接副表,parameter2、3是查询条件
    .Join(departments, teacher => teacher.ID, department => department.TeacherID,
          // lambda表达式,定义连接结果,创建匿名类型对象
        (teacher, department) =>new { DepartmentName = department.Name, TeacherName = $"{teacher.First} {teacher.Last}" });

组合键

IEnumerable<string> query =
    from teacher in teachers
    join student in students on new
    {
        FirstName = teacher.First,
        LastName = teacher.Last
    } equals new
    {
        student.FirstName,
        student.LastName
    }
    select teacher.First + " " + teacher.Last;

// Linq写法
IEnumerable<string> query = teachers
    .Join(students,
        teacher => new { FirstName = teacher.First, LastName = teacher.Last },
        student => new { student.FirstName, student.LastName },
        (teacher, student) => $"{teacher.First} {teacher.Last}"
 );

多联结

var query = from student in students
    join department in departments on student.DepartmentID equals department.ID
    join teacher in teachers on department.TeacherID equals teacher.ID
    select new {
        StudentName = $"{student.FirstName} {student.LastName}",
        DepartmentName = department.Name,
        TeacherName = $"{teacher.First} {teacher.Last}"
    };

// Linq
var query = students
    .Join(departments, student => student.DepartmentID, department => department.ID,
        (student, department) => new { student, department })
    .Join(teachers, commonDepartment => commonDepartment.department.TeacherID, teacher => teacher.ID,
        (commonDepartment, teacher) => new
        {
            StudentName = $"{commonDepartment.student.FirstName} {commonDepartment.student.LastName}",
            DepartmentName = commonDepartment.department.Name,
            TeacherName = $"{teacher.First} {teacher.Last}"
        });

分组

  1. GroupBy - 对共享通用属性进行分组 - group...by
  2. ToLookup - 将元素插入基于键选择器函数的Lookup(一对多字典)

demo

List<int> numbers = [35, 44, 200, 84, 3987, 4, 199, 329, 446, 208];
IEnumerable<IGrouping<int, int>> groupBy1 = from number in numbers group number by number % 2;
// Linq
IEnumerable<IGrouping<int, int>> groupBy2 = numbers.GroupBy(e => e % 2);

foreach (var i in groupBy1)
{
    Console.WriteLine(i.Key == 0 ? "\nEven numbers:" : "\nOdd numbers:");
    foreach (var i1 in i)
    {
        Console.WriteLine(i1);
    }
}

值分组

var groupByFirstLetterQuery =
    from student in students
    let firstLetter = student.LastName[0]
    group student by firstLetter;
// Linq
var groupByFirstLetterQuery = students
    .GroupBy(student => student.LastName[0]);

范围分组

static int GetPercentile(Student s)
{
    double avg = s.Scores.Average();
    return avg > 0 ? (int)avg / 10 : 0;
}

var groupByPercentileQuery =
    from student in students
    let percentile = GetPercentile(student)
    group new
    {
        student.FirstName,
        student.LastName
    } by percentile into percentGroup
    orderby percentGroup.Key
    select percentGroup;
// Linq
var groupByPercentileQuery = students
    .Select(student => new { student, percentile = GetPercentile(student) })
    .GroupBy(student => student.percentile)
    .Select(percentGroup => new
    {
        percentGroup.Key,
        Students = percentGroup.Select(s => new { s.student.FirstName, s.student.LastName })
    })
    .OrderBy(percentGroup => percentGroup.Key);

比较分组

// 匿名类型中的属性将成为Key成员的属性
var groupByHighAverageQuery =
    from student in students
    group new
    {
        student.FirstName,
        student.LastName
    } by student.Scores.Average() > 75 into studentGroup
    select studentGroup;
// Linq
var groupByHighAverageQuery = students
    .GroupBy(student => student.Scores.Average() > 75)
    .Select(group => new
    {
        group.Key,
        Students = group.AsEnumerable().Select(s => new { s.FirstName, s.LastName })
    });

按匿名类型分组

// 第一个键值是首字母,第二个键值是布尔值,
//指定该学生再第一次考试中额得分是否超过85
var groupByCompoundKey =
    from student in students
    group student by new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    } into studentGroup
    orderby studentGroup.Key.FirstLetterOfLastName
    select studentGroup;
// LINQ
var groupByCompoundKey = students
    .GroupBy(student => new
    {
        FirstLetterOfLastName = student.LastName[0],
        IsScoreOver85 = student.Scores[0] > 85
    })
    .OrderBy(studentGroup => studentGroup.Key.FirstLetterOfLastName);

嵌套

var nestedGroupsQuery =
    (from student in students
    group student by student.Year into newGroup1)
    from newGroup2 in
    (from student in newGroup1
    group student by student.LastName)
    group newGroup2 by newGroup1.Key;
// Linq
var nestedGroupsQuery =
    students
    .GroupBy(student => student.Year)
    .Select(newGroup1 => new
    {
        newGroup1.Key,
        NestedGroup = newGroup1
            .GroupBy(student => student.LastName)
    });

异步

核心是TaskTask<T>对象与关键字async和await支持

  • I/O绑定代码,等待一个在async方法中返回Task或Task的操作
    • 如果会“等待”某些内容,则选择I/O绑定,eg.数据库数据,使用async和await
  • 对于CPU绑定代码,等待使用Task.Run方法在后台线程启动的操作
    • 需要执行开销大的计算且重视响应能力,则选择CPU绑定,在另一个线程上使用Task.Run生成工作。如适合并发和并行,还应考虑任务并行库
posted @   颜骏  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示