方法

引用参数

引用参数时,必须在方法的声明和调用时引用ref修饰符

// 方法声明
void ModifyValue(ref int value)
{
    value = 5; // 修改原始变量的值
}

// 方法调用
int value = 10;
ModifyValue(ref value); // 打印结果为 value = 5

接口显示调用

如何调用一个对象的方法?

可以通过new一个对象,然后调用这个对象的方法。伪代码如下

public class ClassObjectExample
{
    public void Method1()
    {
        Debug.Log("方法一");
    }
}

客户端
var obj = new ClassObjectExample();
obj.Method1();

根据依赖倒置原则(高层模块依赖抽象,底层模块也依赖抽象),我们一般会将通过接口去调用方法。为此我们需要定义方法接口。这样子高层模块就不会依赖于实现,而依赖于抽象。修改后的代码如下:

public interface IMethod
{
    void Method1();
}

public class ClassObjectExample : IMethod
{
    public void Method1()
    {
        Debug.Log("方法一");
    }

}

但如果一个类实现两个接口,而这两个接口会有同一个签名方法的话,我们就不知道到底是实现哪一个接口的方法?

public interface IMethod
{
    void Method1();
}

public interface IMethod2
{
    void Method1();
}

public class ClassObjectExample : IMethod, IMethod2
{
    public void Method1()
    {
        Debug.Log("方法一");
    }
}

如上面的代码显示,Method1方法到底是实现的是IMethod的方法还是IMethod2的方法。

为此我们可以做一个实验。

IMethod i = new ClassObjectExample();
i.Method1();

IMethod2 i2 = new ClassObjectExample();
i2.Method1();

// 结果
方法一
方法一

通过运行,我们可以得出结论:ClassObjectExample的Method1方法同时实现了两个接口的具体逻辑。

我们也可以通过显示调用的方式,来消除这种问题。但是类实例无法直接访问显式实现的成员,必须通过接口类型的引用来调用。

public interface IMethod
{
    void Method1();
}

public interface IMethod2
{
    void Method1();
}

public class ClassObjectExample : IMethod, IMethod2
{
    void IMethod.Method1()
    {
        Debug.Log("IMethod的方法");
    }
    
    void IMethod2.Method1()
    {
        Debug.Log("接口2方法");
    }
}

通过接口引用来调用

ClassObjectExample obj = new ClassObjectExample();
// obj无法调用对应的方法
IMethod i = new ClassObjectExample();
i.Method1();

IMethod2 i2 = new ClassObjectExample();
i2.Method1();

故可以得出结论:显示调用可以解决接口方法命名冲突问题。

字符串

ToString()方法

将一个数字变成一个标准数字字符串,且不显示小数部分

字符串.ToString("N0");

// 比如:3000=》3,000
// 比如:10000 =》 10,000

将一个数字变成字符串,且限制数字的小数

F表示希望得到的字符串保留1为小数,如果有多为小数,它会四舍五入到一位。如果少于一位小数,则会补零。

(一个数字).ToString("F1");

// number的值为1.234567, 则最终结果为1.2

EndWith方法

判断字符串的结尾是否是指定的字符

string str1 = "FileG";
if(str1.EndWith("G")){
    Debug.Log("末尾有大G");
}

TrimEnd方法

移除字符串末尾的指定字符

string str1 = "FileG";
string str2 = str1.TrimEnd("G");
// 输出File

数组

数组无非就是声明、赋值、访问

数组的声明

// 一维数组的声明
datatype[] arrayName;

// 二维数组的声明
string [,] names;

数组的赋值

方式一:声明数组的同时,赋值

double[] balance = { 2340.0, 4523.69, 3421.0};

方式二: 使用new关键字

// int [] marks = new int[5]  { 99,  98, 92, 97, 95};
// 可以省略数组的大小
int [] marks = new int[]  { 99,  98, 92, 97, 95};

泛型

泛型就是将类型做参数的一门技术,可以将其作为一个类型占位符。

// 传入的T是什么类型,T就是什么类型。所以说它是将类型作为参数
class Test<T>
{
    public T value;
}

泛型的分类

1. 泛型类

class Test2<T>
{
    public T value;
}

2. 泛型函数(结构:函数名<泛型占用字符>(参数列表)

// 普通类的泛型方法
class Test3
{
    public void Test3Fun<T>()
    {
    }
}

// 泛型类的泛型方法
class Test4<T>
{
    public void Test3Fun<K>(K v)
    {
    }
}

泛型约束

1. 值类型约束

class Animal<T> where T : struct // 限制 T 为值类型,只能是枚举和结构int、float
{
}

2. 引用类型约束

class Aniaml<T> where T : class // 限制 T 为引用类型,只能是类、接口、委托
{
}

3. 公共无参构造方法约束

约束传入的类型T必须有无参构造函数

class Test1<T>where T : new()
{
    public T value;
}

4. 类约束

传入的类型只能是Test2类型或者是Test2的子类

class Test2{}
class Test1<T>where T : Test2
{
    public T value;
}

5. 接口约束

传入的类型T只能是Imove接口,或者是实现Imove接口的类

interface IMove{}

class Test1<T>where T : IMove
{
    public T value;
}

6. 另一个泛型约束

传入的类型T只能是U类,或者是U类的子类

class Test1<T,U>where T : U
{
    public T value;
}

7. 多个泛型有约束

class Test1<T,K>where T : class where K : new()
{
    public T value;
}

8. 泛型的组合

传入的参数T必须满足2个条件:引用类型的、且必须有无参构造函数

class Test1<T>where T : class,new()
{
    public T value;
}

逆变和协变

逆变和协变,都是修饰泛型的。逆变in、协变out

1. in修饰的泛型

<in T> T只能作为参数,不能作为返回值

public delegate void myFun<in T>(T t);

public delegate int Comparison<in T>(T x, T y);

2. out修饰的泛型

当out修饰T时,T只能被作为返回值

public delegate T myFun<out T>();

3. out和in的组合

T只能被作为参数,K只能被作为返回值

public delegate K myFun<in T, out K>(T t);

面向对象

迭代器

特性

委托、事件、匿名函数、Lamda表达式

容器

ArrayList

常用API

Add

AddRange

Remove

RemoveAt

Clear

List

Stack

Queue

HashTable

Set

HashSet

Dictionary

LinkedList

反射

DateTime类

DateTime.MinValue

返回一个日期为 0001年01月01日 00:00:00

ToString("O")

标准时间格式:yyyy-MM-ddTHH-mm-ss.fffffffK

时区

时区:在一个区域内的标准时间。

UTC:全球统一的时间,通过偏移可以得到时区。

UTC+8:中国的标准时间
UTC-5:美国东部标准时间

解析字符串

DateTime.Parse(日期字符串)

将一个表示日期和时间的字符串解析为DateTime对象

(DateTime对象).Date

Date属性会返回日期对象的部分,yyyy-MM-dd 00:00:00,会忽略时间部分。

AddDays(天数)

日期向后延长得到的DateTime对象

文件系统与IO

拼接路径Path.Combine

将一个路径和多个路径拼接起来,这个方法可以确保路径分隔符正确无误,无论是在 Windows、MacOS 还是 Linux 等不同操作系统上都能正常工作,这使得路径的拼接更加方便和可靠。

string s1 = "hello";
string s2 = "world";
string s3 = Path.Combine(s1, s2);

输出:

TimeSpan类

延迟加载Lazy

当我们需要一个对象的时候,并不立即创建它,而是在第一次访问的时候才创建。这样可以提高程序的性能,特别是当对象的创建成本很高,或者可能在某些情况下根本不需要用到这个对象的时候。这样的话,延迟加载可以节省资源,加快程序启动速度。

构造函数

// 创建Lazy实例,提供初始化逻辑
Lazy<ExpensiveObject> lazyObject = new Lazy<ExpensiveObject>(() => new ExpensiveObject());

// 访问Value属性触发初始化
ExpensiveObject obj = lazyObject.Value;

检测是否已经初始化

if (lazyObject.IsValueCreated)
{
    Console.WriteLine("对象已初始化。");
}