C#高级编程核心知识点

1、函数参数

(1)按值传递参数

public void swap(int x, int y)

(2)按引用传递参数

public void swap(ref int x, ref int y)

2、Null可空类型

(1)1个?

? 单问号用于对 int、double、bool 等无法直接赋值为 null 的数据类型进行 null 的赋值,意思是这个数据类型是 Nullable 类型的。 示例: int i; // 默认值为0 int? ii; // 默认值为null。

(2)2个??

如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。 num3 = num1 ?? 5.34; // num1 如果为空值则返回 5.34

(3)?. 控制传播运算符

空值传播运算符(?.)用来判断 类 的对象是否为空,为空返回空,否则返回 对应的 字段 或 属性 值。

示例:

ChangeGamePausedState?.Invoke(value); 若ChangeGamePausedState不为null就Invoke(),为null就不执行。

3、结构体和类的区别

结构体struct是值类型,修改结构体实例不影响其他实例

类是引用类型,修改类实例会影响其他实例

4、using命名空间

定义命名空间: namespace namespace_name

using 关键字表明程序使用的是给定命名空间中的名称。例如,我们在程序中使用 System 命名空间,其中定义了类 Console。我们可以只写:

Console.WriteLine ("Hello there");

我们可以写完全限定名称,如下: System.Console.WriteLine("Hello there");

5、预处理器指令

(1)指令列表

(2)示例

cs 复制代码
#define DEBUG

#if DEBUG
    Console.WriteLine("Debug mode");
#elif RELEASE
    Console.WriteLine("Release mode");
#else
    Console.WriteLine("Other mode");
#endif

#warning This is a warning message
#error This is an error message

#region MyRegion
    // Your code here
#endregion

#line 100 "MyFile.cs"
    // The next line will be reported as line 100 in MyFile.cs
    Console.WriteLine("This is line 100");
#line default
    // Line numbering returns to normal

#pragma warning disable 414
    private int unusedVariable;
#pragma warning restore 414

#nullable enable
    string? nullableString = null;
#nullable disable

(3)注意事项

  1. 预处理器指令不是语句,不以**;**结束
  2. 提高代码可读性:使用#region可以帮助分割代码块,提高代码的组织性
  3. 条件编译:通过#if等指令可以在开发和生产环境中编译不同的代码,方便调试和发布
  4. 警告和错误:通过#warning和#error可以在编译时提示开发人员注意特定问题

6、Conditional特性

这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。

示例:

cs 复制代码
#define DEBUG
using System;
using System.Diagnostics;
public class Myclass
{
    [Conditional("DEBUG")]
    public static void Message(string msg)
    {
        Console.WriteLine(msg);
    }
}
class Test
{
    static void function1()
    {
        Myclass.Message("In Function 1.");
        function2();
    }
    static void function2()
    {
        Myclass.Message("In Function 2.");
    }
    public static void Main()
    {
        Myclass.Message("In Main function.");
        function1();
        Console.ReadKey();
    }
}

7、Obsolete过时特性

语法格式:

[Obsolete(

message

)]

[Obsolete(

message,

iserror

)]

参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。

示例:

cs 复制代码
using System;
public class MyClass
{
   [Obsolete("Don't use OldMethod, use NewMethod instead", true)]
   static void OldMethod()
   { 
      Console.WriteLine("It is the old method");
   }
   static void NewMethod()
   { 
      Console.WriteLine("It is the new method"); 
   }
   public static void Main()
   {
      OldMethod();
   }
}

当尝试编译该程序时,编译器会给出一个错误消息说明:

Don't use OldMethod, use NewMethod instead

8、AttributeUsage特性

描述了如何使用一个自定义特性类,它规定了特性可应用到项目的类型。

语法格式:

[AttributeUsage(

validon,

AllowMultiple=allowmultiple,

Inherited=inherited

)]

  1. 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
  2. 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
  3. 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。

9、创建自定义特性

.Net框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索,类似spring中@interface注解。

创建并使用自定义特性的4个步骤:

  1. 声明自定义特性
  2. 构建自定义特性
  3. 在目标程序元素上应用自定义特性
  4. 通过反射访问特性

(1)声明自定义特性及构建自定义特性

一个新的自定义特性应派生自System.Attribute类。

cs 复制代码
// 一个自定义特性 BugFix 被赋给类及其成员
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]

public class DeBugInfo : System.Attribute
{
  private int bugNo;
  private string developer;
  private string lastReview;
  public string message;

  public DeBugInfo(int bg, string dev, string d)
  {
      this.bugNo = bg;
      this.developer = dev;
      this.lastReview = d;
  }

  public int BugNo
  {
      get
      {
          return bugNo;
      }
  }
  public string Developer
  {
      get
      {
          return developer;
      }
  }
  public string LastReview
  {
      get
      {
          return lastReview;
      }
  }
  public string Message
  {
      get
      {
          return message;
      }
      set
      {
          message = value;
      }
  }
}

(2)应用自定义特性

通过把特性放置在紧接着它的目标之前,来应用该特性:

cs 复制代码
[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
[DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
class Rectangle
{
  // 成员变量
  protected double length;
  protected double width;
  public Rectangle(double l, double w)
  {
      length = l;
      width = w;
  }
  [DeBugInfo(55, "Zara Ali", "19/10/2012",
  Message = "Return type mismatch")]
  public double GetArea()
  {
      return length * width;
  }
  [DeBugInfo(56, "Zara Ali", "19/10/2012")]
  public void Display()
  {
      Console.WriteLine("Length: {0}", length);
      Console.WriteLine("Width: {0}", width);
      Console.WriteLine("Area: {0}", GetArea());
  }
}

10、Reflection反射

反射:程序访问、检测和修改它本身状态或行为的一种能力。

优点:灵活,允许程序创建和控制任何类的对象而无需提前硬编码目标类。

缺点:性能慢、可维护性差。

(1)查看元数据

System.Reflection 类的 MemberInfo 对象需要被初始化,用于发现与类相关的特性(attribute)。为了做到这点,您可以定义目标类的一个对象,如下:

System.Reflection.MemberInfo info = typeof(MyClass);

(2)反射中的Invoke方法

在反射中,可以使用Invoke方法来调用队形的方法、获取或设置对象的属性值等。

这使得在运行时动态地调用和操作对象成为可能。

Invoke方法有2个参数,第1个参数是要在其上调用方法的对象实例(如果方法是静态的则为null),第2个参数是要传递给方法的参数数组。

示例:

cs 复制代码
class MyClass
{
    public void printMessage(string message)
    {
        Console.WriteLine(message);
    }
}

class Program
{

	static void Main(string[] args) {
    	MyClass myClass = new MyClass();
		System.Reflection.MethodInfo method = typeof(MyClass).GetMethod("printMessage");
		method.Invoke(myClass, new object[] { "test invoke!" });

		Console.ReadLine();
   }
}

11、属性

属性是类和结构体中用于封装数据的成员。

它可以看作是对字段的包装器,通常由get和set访问器组成。

可对其进行赋值和读取。

属性名的首字母为大写。

(1)基本语法

cs 复制代码
public class Person
{
    private string name;

    public string Name
    {
        get { return name; }
        set { name = value; }
    }
}

说明:Name属性封装了私有字段name。get访问器用于获取字段值,而set访问器用于设置字段值。value是赋值的值,固定写法。

(2)自动实现的属性

如果只需要一个简答的属性,C#允许使用自动实现的属性,这样就不需要显示地定义字段。

cs 复制代码
public class Person
{
    public string Name { get; set; }
}

在这种情况下,编译器会自动为Name属性生成一个私有的匿名字段来存储值。

(3)计算属性

属性也可以是计算的,不依赖于字段。

cs 复制代码
public class Rectangle
{
    public int Width { get; set; }
    public int Height { get; set; }

    public int Area
    {
        get { return Width * Height; }
    }
}

(4)示例

cs 复制代码
using System;
namespace runoob
{
   class Student
   {

      private string code = "N.A";
      private string name = "not known";
      private int age = 0;

      // 声明类型为 string 的 Code 属性
      public string Code
      {
         get
         {
            return code;
         }
         set
         {
            code = value;
         }
      }
   
      // 声明类型为 string 的 Name 属性
      public string Name
      {
         get
         {
            return name;
         }
         set
         {
            name = value;
         }
      }

      // 声明类型为 int 的 Age 属性
      public int Age
      {
         get
         {
            return age;
         }
         set
         {
            age = value;
         }
      }
      public override string ToString()
      {
         return "Code = " + Code +", Name = " + Name + ", Age = " + Age;
      }
    }
    class ExampleDemo
    {
      public static void Main()
      {
         // 创建一个新的 Student 对象
         Student s = new Student();
            
         // 设置 student 的 code、name 和 age
         s.Code = "001";
         s.Name = "Zara";
         s.Age = 9;
         Console.WriteLine("Student Info: {0}", s);
         // 增加年龄
         s.Age += 1;
         Console.WriteLine("Student Info: {0}", s);
         Console.ReadKey();
       }
   }
}

12、Delegate委托

委托类似于C中的函数指针。

它是存有对某个方法的引用的一种引用类型变量,引用可在运行时被改变。

所有的委托都派生自System.Delegate类。

应用场景:事件和回调方法。

(1)声明委托

示例:

public delegate int MyDelegate (string s);

上面的委托可被用于引用任何一个带有一个单一的string参数的方法,并返回一个int类型的变量。

(2)实例化委托

委托对象必须使用new关键字来创建,且与一个特定的方法有关。

当创建委托时,传递到new语句的参数就像方法调用一样书写,但是不带有参数。

示例:

public delegate void printString(string s);

...

printString ps1 = new printString(WriteToScreen);

printString ps2 = new printString(WriteToFile);

(3)Invoke方法

委托类型具有一个名为Invoke的方法,用于调用委托所引用的方法。例如,如果有一个委托myDelegate,可以使用myDelefate.Invoke()来执行委托所引用的方法。

示例:

cs 复制代码
class Program{
public delegate void MyDelegate(string message);

public static void PrintMessage(string message)
{
   Console.WriteLine(message);
}
	static void Main(string[] args) {

     MyDelegate myDelegate = new MyDelegate(PrintMessage);
     myDelegate.Invoke("Hello here!");

     Console.ReadLine();
	}
}

(4)完整实例

cs 复制代码
using System;

delegate int NumberChanger(int n);
namespace DelegateAppl
{
   class TestDelegate
   {
      static int num = 10;
      public static int AddNum(int p)
      {
         num += p;
         return num;
      }

      public static int MultNum(int q)
      {
         num *= q;
         return num;
      }
      public static int getNum()
      {
         return num;
      }

      static void Main(string[] args)
      {
         // 创建委托实例
         NumberChanger nc1 = new NumberChanger(AddNum);
         NumberChanger nc2 = new NumberChanger(MultNum);
         // 使用委托对象调用方法
         nc1(25);
         Console.WriteLine("Value of Num: {0}", getNum());
         nc2(5);
         Console.WriteLine("Value of Num: {0}", getNum());
         Console.ReadKey();
      }
   }
}

(5)委托的多播

委托对象可使用"+"运算符进行合并,只有相同类型的委托可被合并。

"-"运算符可用于从合并的委托中移除组件委托。

用途:执行一连串的方法。

示例片段:

cs 复制代码
NumberChanger nc; 
NumberChanger nc1 = new NumberChanger(AddNum); 
NumberChanger nc2 = new NumberChanger(MultNum); 
nc = nc1; nc += nc2; // 调用多播 nc(5);

13、Event事件

用于将特定的事件通知发送给订阅者。

关键点:

  1. 声明委托:定义事件要使用的委托类型,委托是一个函数签名
  2. 声明事件:使用event关键字声明一个事件
  3. 触发事件:在适当的时候调用事件,通过所有订阅者
  4. 订阅和取消订阅事件:其他类通过+=和-=运算符订阅和取消订阅事件。

示例:(该示例是所有Event处理的模板)

cs 复制代码
using System;

namespace EventDemo
{
    // 定义一个委托类型,用于事件处理程序
    public delegate void NotifyEventHandler(object sender, EventArgs e);

    // 发布者类
    public class ProcessBusinessLogic
    {
        // 声明事件
        public event NotifyEventHandler ProcessCompleted;

        // 触发事件的方法
        protected virtual void OnProcessCompleted(EventArgs e)
        {
            ProcessCompleted?.Invoke(this, e);
        }

        // 模拟业务逻辑过程并触发事件
        public void StartProcess()
        {
            Console.WriteLine("Process Started!");

            // 这里可以加入实际的业务逻辑

            // 业务逻辑完成,触发事件
            OnProcessCompleted(EventArgs.Empty);
        }
    }

    // 订阅者类
    public class EventSubscriber
    {
        public void Subscribe(ProcessBusinessLogic process)
        {
            process.ProcessCompleted += Process_ProcessCompleted;
        }

        private void Process_ProcessCompleted(object sender, EventArgs e)
        {
            Console.WriteLine("Process Completed!");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ProcessBusinessLogic process = new ProcessBusinessLogic();
            EventSubscriber subscriber = new EventSubscriber();

            // 订阅事件
            subscriber.Subscribe(process);

            // 启动过程
            process.StartProcess();

            Console.ReadLine();
        }
    }
}

说明:

1)定义委托类型

public delegate void NotifyEventHandler(object sender, EventArgs e);

这是一个委托类型,它定义了事件处理程序的签名。通常使用 EventHandler 或 EventHandler<TEventArgs> 来替代自定义的委托。

2)声明事件

public event NotifyEventHandler ProcessCompleted;

这是一个使用NotifyEventHandler委托类型的事件。

3)触发事件

protected virtual void OnProcessCompleted(EventArgs e)

{

ProcessCompleted?.Invoke(this, e);

}

这是一个受保护的方法,用于触发事件。使用 ?.Invoke 语法来确保只有在有订阅者时才调用事件。

4)订阅和取消订阅事件

process.ProcessCompleted += Process_ProcessCompleted;

订阅者使用 += 运算符订阅事件,并定义事件处理程序 Process_ProcessCompleted。

解读:process属于发布者类,所以订阅的本质就是在发布者的Event上注册了订阅者收到事件后的处理函数,该处理函数必须符合Event对应的delegate的规范

示例2:(验证上面红色字体的内容)

cs 复制代码
using System;
namespace SimpleEvent
{
  using System;
  /***********发布器类***********/
  public class EventTest
  {
    private int value;

    public delegate void NumManipulationHandler();


    public event NumManipulationHandler ChangeNum;
    protected virtual void OnNumChanged()
    {
      if ( ChangeNum != null )
      {
        ChangeNum(); /* 事件被触发 */
      }else {
        Console.WriteLine( "event not fire" );
        Console.ReadKey(); /* 回车继续 */
      }
    }


    public EventTest()
    {
      int n = 5;
      SetValue( n );
    }


    public void SetValue( int n )
    {
      if ( value != n )
      {
        value = n;
        OnNumChanged();
      }
    }
  }


  /***********订阅器类***********/

  public class subscribEvent
  {
    public void printf()
    {
      Console.WriteLine( "event fire" );
      Console.ReadKey(); /* 回车继续 */
    }
  }

  /***********触发***********/
  public class MainClass
  {
    public static void Main()
    {
      EventTest e = new EventTest(); /* 实例化对象,第一次没有触发事件 */
      subscribEvent v = new subscribEvent(); /* 实例化对象 */
      e.ChangeNum += new EventTest.NumManipulationHandler( v.printf ); /* 注册 */
      e.SetValue( 7 );
      e.SetValue( 11 );
    }
  }
}

14、集合之字典

字典各个语法不同,java用HashMap,python用dict,C#用Dictionary

示例:

var dict = new Dictionary<int,int>();

15、泛型

泛型允许您编写一个可以与任何数据类型一起工作的类或方法。

示例:

cs 复制代码
public class MyGenericArray<T>
{
    private T[] array;
    public MyGenericArray(int size)
    {
        array = new T[size];
    }

    public T getItem(int index)
    {
        return array[index];
    }

    public void setItem(int index, T value)
    {
        array[index] = value;
    }

}

internal class Program
{
    static void Main(string[] args) {
        MyGenericArray<int> intArray = new MyGenericArray<int>(5);
        for(int c = 0; c < 5; c++)
        {
            intArray.setItem(c, c * 5);
        }
        for(int c = 0; c < 5; c++)
        {
            Console.WriteLine(intArray.getItem(c));
        }
        Console.ReadKey();
    }
}

泛型约束:

示例:

public class CacheHelper<T> where T:new() { }

泛型限定条件:

  1. T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型)
  2. T:类 (类型参数必须是引用类型,包括任何类、接口、委托或数组类型)
  3. T:new() (类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时new() 约束必须最后指定)
  4. T:<基类名> 类型参数必须是指定的基类或派生自指定的基类
  5. T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

16、匿名方法

匿名方法是一种没有名字的方法,可以在代码中定义和使用。

使用Lambda表达式,语法为:

(parameters) => expression

// 或

(parameters) => { statement; }

示例:

Func<int, int, int> add = (a, b) => a + b;

Console.WriteLine(add(2, 3)); // 输出 5

相关推荐
冠位观测者2 小时前
【Leetcode 热题 100】208. 实现 Trie (前缀树)
数据结构·算法·leetcode
kittygilr5 小时前
matlab中的cell
开发语言·数据结构·matlab
花心蝴蝶.6 小时前
Map接口 及其 实现类(HashMap, TreeMap)
java·数据结构
taoyong0017 小时前
代码随想录算法训练营第十五天-二叉树-110.平衡二叉树
数据结构·算法
c1assy8 小时前
DP动态规划+贪心题目汇总
数据结构·算法·leetcode·贪心算法·动态规划
代码小将9 小时前
PTA数据结构编程题7-1最大子列和问题
数据结构·c++·笔记·学习·算法
yangjiwei02079 小时前
数据结构-排序
数据结构·python
坊钰9 小时前
【Java 数据结构】合并两个有序链表
java·开发语言·数据结构·学习·链表
抓住鼹鼠不撒手9 小时前
力扣 429 场周赛-前两题
数据结构·算法·leetcode
南宫生10 小时前
力扣-数据结构-3【算法学习day.74】
java·数据结构·学习·算法·leetcode