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)注意事项
- 预处理器指令不是语句,不以**;**结束
- 提高代码可读性:使用#region可以帮助分割代码块,提高代码的组织性
- 条件编译:通过#if等指令可以在开发和生产环境中编译不同的代码,方便调试和发布
- 警告和错误:通过#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
)]
- 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
- 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
- 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。
9、创建自定义特性
.Net框架允许创建自定义特性,用于存储声明性的信息,且可在运行时被检索,类似spring中@interface注解。
创建并使用自定义特性的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事件
用于将特定的事件通知发送给订阅者。
关键点:
- 声明委托:定义事件要使用的委托类型,委托是一个函数签名
- 声明事件:使用event关键字声明一个事件
- 触发事件:在适当的时候调用事件,通过所有订阅者
- 订阅和取消订阅事件:其他类通过+=和-=运算符订阅和取消订阅事件。
示例:(该示例是所有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() { }
泛型限定条件:
- 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