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)示例
#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)注意事项
- 预处理器指令不是语句,不以;结束
- 提高代码可读性:使用#region可以帮助分割代码块,提高代码的组织性
- 条件编译:通过#if等指令可以在开发和生产环境中编译不同的代码,方便调试和发布
- 警告和错误:通过#warning和#error可以在编译时提示开发人员注意特定问题
6、Conditional特性
这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。
示例:
#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(编译器生成一个警告)。
示例:
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
)]
- 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
- 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
- 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。
9、创建自定义特性
.Net框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索,类似spring中@interface注解。
创建并使用自定义特性的4个步骤:
- 声明自定义特性
- 构建自定义特性
- 在目标程序元素上应用自定义特性
- 通过反射访问特性
(1)声明自定义特性及构建自定义特性
一个新的自定义特性应派生自System.Attribute类。
// 一个自定义特性 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)应用自定义特性
通过把特性放置在紧接着它的目标之前,来应用该特性:
[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个参数是要传递给方法的参数数组。
示例:
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)基本语法
public class Person
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
}
说明:Name属性封装了私有字段name。get访问器用于获取字段值,而set访问器用于设置字段值。value是赋值的值,固定写法。
(2)自动实现的属性
如果只需要一个简答的属性,C#允许使用自动实现的属性,这样就不需要显示地定义字段。
public class Person
{
public string Name { get; set; }
}
在这种情况下,编译器会自动为Name属性生成一个私有的匿名字段来存储值。
(3)计算属性
属性也可以是计算的,不依赖于字段。
public class Rectangle
{
public int Width { get; set; }
public int Height { get; set; }
public int Area
{
get { return Width * Height; }
}
}
(4)示例
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()来执行委托所引用的方法。
示例:
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)完整实例
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)委托的多播
委托对象可使用”+”运算符进行合并,只有相同类型的委托可被合并。
“-”运算符可用于从合并的委托中移除组件委托。
用途:执行一连串的方法。
示例片段:
NumberChanger nc;
NumberChanger nc1 = new NumberChanger(AddNum);
NumberChanger nc2 = new NumberChanger(MultNum);
nc = nc1; nc += nc2; // 调用多播 nc(5);
(6)Action使用
Action是一个预定义的泛型的委托类型。
Action是一种简化版的委托,可以方便地定义和调用没有返回值并且可以接受任意数量参数的方法。
class Program
{
public static void SayHello()
{
Console.WriteLine("third");
}
public static void PrintSum(int a, int b)
{
Console.WriteLine($"fourth:{a} {b}");
}
static void Main(string[] args) {
// 匿名不带参数
Action anonymousAction = () => Console.WriteLine("first");
// lambda不带参数
Action lambdaAction = () => { Console.WriteLine("second"); };
// 命名方法不带参数
Action noParamAction = SayHello;
// 命名方法带参数
Action<int, int> withParamAction = PrintSum;
// 调用方法1
anonymousAction();
lambdaAction();
noParamAction();
withParamAction(1, 2);
// 调用方法2
Action chainAction = anonymousAction;
chainAction += lambdaAction;
chainAction += noParamAction;
chainAction();
Console.ReadKey();
}
}
13、Event事件
用于将特定的事件通知发送给订阅者。
关键点:
- 声明委托:定义事件要使用的委托类型,委托是一个函数签名
- 声明事件:使用event关键字声明一个事件
- 触发事件:在适当的时候调用事件,通过所有订阅者
- 订阅和取消订阅事件:其他类通过+=和-=运算符订阅和取消订阅事件。
示例:(该示例是所有Event处理的模板)
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:(验证上面红色字体的内容)
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、泛型
泛型允许您编写一个可以与任何数据类型一起工作的类或方法。
示例:
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() { }
泛型限定条件:
- T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型)
- T:类 (类型参数必须是引用类型,包括任何类、接口、委托或数组类型)
- T:new() (类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时new() 约束必须最后指定)
- T:<基类名> 类型参数必须是指定的基类或派生自指定的基类
- T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
16、匿名方法
匿名方法是一种没有名字的方法,可以在代码中定义和使用。
使用Lambda表达式,语法为:
(parameters) => expression
// 或
(parameters) => { statement; }
示例:
Func<int, int, int> add = (a, b) => a + b;
Console.WriteLine(add(2, 3)); // 输出 5