In
/// <summary>
/// 封装一个方法,该方法只有一个参数并且不返回值。
/// </summary>
/// <typeparam name="T">此委托封装的方法的参数类型。</typeparam>
/// <param name="obj">此委托封装的方法的参数。</param>
public delegate void GameFrameworkAction<in T>(T obj);
in 表示传入的参数不会被修改,哪怕是传入的是引用类型
const与readonly
1. const修饰的局部变量或字段属于静态常量,静态常量是在程序编译时就确定其值;readonly通常用来修饰字段,属于动态常量,动态常量是在运行时确定其值的。
2. 由于const是编译时常量,所以声明时必须初始化,而且const修饰的局部变量或字段不能添加static修饰符;readonly是运行时常量,其修饰的字段可以在声明或构造函数中进行初始化,readonly修饰的字段可以添加static修饰符,在被赋值后,其值也是不可以修改的。
3. const修饰的常量注重效率,没有内存消耗,可以修饰的类型包括基元类型、字符串类型、枚举类型;readonly修饰的常量注重灵活性,需要内存消耗,可以修饰任何类型。
C#中有两种常量类型,分别为readonly(运行时常量)与const(编译时常量),本文将就这两种类型的不同特性进行比较并说明各自的适用场景。
工作原理
readonly为运行时常量,程序运行时进行赋值,赋值完成后便无法更改,因此也有人称其为只读变量。
const为编译时常量,程序编译时将对常量值进行解析,并将所有常量引用替换为相应值。
const: 用const修饰符声明的成员叫常量,是在编译期初始化并嵌入到客户端程序
static readonly: 用static readonly修饰符声明的成员依然是变量,只不过具有和常量类似的使用方法:通过类进行访问、初始化后不可以修改。但与常量不同的是这种变量是在运行期初始化。
1.const是不变常量,在编译的时候就需要有确定的值,只能用于数值和字符串,或者引用类型只能为null.(这里为什么要把字符串单独拿出来?是因为字符串string是引用类型,但是使用的时候却感觉是值类型,它是一种特殊的引用类型,后面会详细说),struct也不能用const标记。const可以修饰class的字段或者局部变量,不能修饰属性。而readonly仅仅用于修饰class的字段,不能修饰属性。const是属于类级别而不是实例对象级别,不能跟static一起使用。而readonly既可以是类级别也可以是实例级别,它可以与static一起使用。
2.readonly是只读的意思,表示不能进行写操作。最重要的是它在程序运行时才会去求值。它可以是任意类型,当然可以是object,数组,struct,它必须在构造函数或者初始化器中初始化,初始化完成之后不能被修改。通常可以定义一个readonly值为DateTime的常量。而const却无法指定为DateTime类型。
一、const 与 readonly 以及static readonly的区别。
const:const是常量;readonly是实例只读变量;static readonly是静态只读变量
区别可以从以下几个方面分析:
1、内存分配
const是常量,C#编译器编译源代码时,会将const常量直接编译成IL代码,因此,它不存在分配内存的问题。
readonly 修饰只读实例字段时,只能在调用构造函数时才能对它进行初始化,而调用构造函数对它初始化,必然涉及到内存分配问题。readonly只能在对象除此创建时写入数值,除非用反射来修改readonly字段。
当readonly前面加static关键字变成static readonly时,该变量变成静态只读字段,此时,它是类型状态的一部分,在该变量所在类型加载到应用程序域时,会为他在动态内存内分配内存。
2、地址(指针)
由1知,const常量在编译时是直接嵌入代码的,因此在运行时就不需要为常量分配任何内存,因此它的地址我们也就无法得知,也就不能通过引用传递常量。
而readonly加在变量前面变成实例只读字段时,该变量会在对象调用构造函数的过程中为它分配内存,因此它也就有实实在在的地址(指针)。
static readonly修饰变量时,该变量是静态只读字段,在类型加载时会给他分配内存,因此该变量也是有指针的。
我们在定义我们的常量的时候,优先选择const,const的效率更高,如果const不能用,比如:我们不是用常数,而是用一个函数的返回值给常量初始化,那么我们就使用static readonly。
例子:static readonly int a = SomeStaticClass.GetInt();
如果用const,就会报compile-time error
关于readonly关键字:
readonly可以分为static readonly和没有static的readonly:
static readonly: 因为定义了static,所以是属于类的常量(const不用加static,因为const一定是属于类的常量),被static readonly定义的常量可以在定义的时候初始化话,也可以在静态构造函数中初始化,下面是一个静态构造函数的例子,静态构造函数除了static之外不能有任何的限制符,因为它一定是public的,并且它也不能有如何的参数,因为静态构造函数是C#在runtime的时候调用的。我们唯一知道的是静态构造函数(静态构造函数可以存在于静态或者非静态类中)是在类中的静态成员被引用之前或者对象被创建之前调用的,而且只会被调用一次,
public static class A
{
static A() // No modifier, and parameterless
{
}
}
const只能在定义的时候被初始化,所以const其实是在compile time的时候被初始化的,而readonly是在runtime的时候被初始化的。
没有static限制的readonly:和static限制的readonly只有一个区别,前者是属于类的常量,后者是属于对象的常量,和前面说的一样,被readonly修饰的常量只能在定义的时候初始化,或者在constructor里面初始化。
总结
const: compile time初始化,效率高,属于类(所以不用加static)的常量,只能用常数来给它赋值。
readonly:runtime初始化,效率没有const高,如果前面加上了static,那么它就是属于类的常量,如果没有加,就是属于对象的常量,无论加没有加static,都可以在定义的时候初始化,而如果加了static,还可以在static constructor里面初始化,如果没有加static,还可以在一般的constructor里面初始化。readonly没有const限制那么大,因为readonly在runtime而不是compile time初始化的,所以我们可以用常数或者用函数的返回值来给他初始化(比如:readonly int a = GetInt(),const就不行)
static constructor只会在runtime的时候调用一次,不能有任何的修饰符(除了static)和参数(因为它一定是public的,而且被C#自动调用的),它的调用时机我们不能掌握,只知道它在对象被创建之前或者在static field被引用之前调用
default(t)
在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T:
-
T 是引用类型还是值类型。
-
如果 T 为值类型,则它是数值还是结构。
给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型会返回 null,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或 null 的每个结构成员,具体取决于这些结构是值类型还是引用类型。对于可以为 null 的值类型,默认返回 System.Nullable<T>,它像任何结构一样初始化。
///之所以会用到default关键字,
///是因为需要在不知道类型参数为值类型还是引用类型的情况下,为对象实例赋初值。
///考虑以下代码:
class TestDefault<T>
{
public T foo()
{
T t = null; //???
return t;
}
}
///如果我们用int型来绑定泛型参数,那么T就是int型,
///那么注释的那一行就变成了 int t = null;显然这是无意义的。
///为了解决这一问题,引入了default关键字:
class TestDefault<T>
{
public T foo()
{
return default(T);
}
}
default(string) // null
default(int) // 0
default(int?) // null
default(dynamic) // null
default(DateTime) // 0001/01/01 0:00:00
default(DateTime?) // null
operator
使用 operator
关键字重载内置运算符,或在类或结构声明中提供用户定义的转换。
C#中,操作符都是一些静态方法,其返回值表示操作结果,其参数是操作数.当我们为一个类创建操作符时,我们称为"重载(OverLoad)"该操作符,与成员方法重载很像.要重载加法操作符(+),应这样写:
public static Fraction operator+(Fraction lhs,Fraction rhs);
将参数取名为lhs和rhs是我的习惯,参数名lhs代表"lefthand side"(左手边),这样可以提醒我第一参数代表操作的左边.类似的,rhs代表"righthand side"(右手边).
public static Student operator -(Student a, Student b)
{
return new Student
{
Chinese = a.Chinese - b.Chinese,
Math = a.Math - b.Math
};
}
C#的操作符号重载语法是在要重载的操作符后写上关键字operator.该关键字是一个方法修饰符.因此要重载加法操作符(+),应写成operator+.
operator用于定义类型转化时可采用2种方式,隐式转换(implicit)和显示转换(explicit)
public static implicit 目标类型(被转化类型 变量参数)
{
return 目标类型结果;
}
public static explicit 目标类型(被转化类型 变量参数)
{
return 目标类型结果;
}
explicit 和 implicit
explicit 和 implicit 属于转换运算符,如用这两者可以让我们自定义的类型支持相互交换。
explicit 表示显式转换,如从 A -> B 必须进行强制类型转换(B = (B)A)
implicit 表示隐式转换,如从 B -> A 只需直接赋值(A = B)
internal的访问范围误区释疑
一、internal
Internal:访问仅限于当前程序集。
protected internal:访问限制到当前程序集或从包含派生的类型的类别。
private:访问仅限于包含类型。
二、认知误区
internal,英文含义是“内部的”,这时候基础不扎实的同学,可能就混淆了这个“内部的”的确切含义,到底是指“同一命名空间”的内部,还是“同一程序集”的内部,本人就是突然被问到这个问题的时候,真的就犹豫了,而且曾经一度以为就是“同一命名空间”的内部(话外:我们太多时候都自以为了,其实只要自己稍微MSDN查一下,就很清楚了)。
第一部分已经摘抄了微软MSDN官方的解释,其实这个内部就是“同一程序集”的内部,也就是说,internal修饰的方法或者属性,只要是在同一个程序集的中的其他类都可以访问,如果二者不在同一命名空间,只要使用using引用上相应的命名空间即可,这里,从另外一个方面也间接看出命名空间并不是界定访问级别的,而是保证全局的类唯一性的,下面就从现实生活中解释下internal的实际作用。
三、释疑
都说艺术源于生活,编程也是一门艺术,所以一样也是可以成生活中找到相应的场景,下面我们以实际生活场景来描述internal修饰符的作用。
某公司的某技术中心,发文规定即日起,中心内的“打印机”仅限本中心的各个部门使用,其他中心的人员不能使用。这个现实生活的场景中,技术中心其实就是个程序集,而每个部门相当于不同的类,当然部门可以有相应的标签,相当于不同的命名空间,其实就是进行逻辑划分,职责不同的部门属于不同的命名空间就可以理解了。而我们的“主角”打印机的旁边就会被贴上公告“本中心的打印机只能本中心使用。。。。”,此时就相当于给打印机打上了internal修饰符了。其他中心的同学们,再也用不上本中心的打印机了。
下面我们用代码来描述上面的场景:
- 我们先在一个公共的类库中(程序集)定义一个打印机的类
namespace CommonAsset
{
public class Printer
{
private bool _isBad;
/// <summary>
/// 打印机是否坏了
/// </summary>
public bool IsBad
{
get { return _isBad; }
set { _isBad = value; }
}
public void Print()
{
Console.WriteLine("开始打印!");
}
}
}
然后我们在创建一个中心A(程序集),分别定义各个部门,且属于不同的命名空间
-
namespace CenterA.Administration { public class DepartmentB { internal Printer PrinterB { get; set; } public DepartmentB() { PrinterB = new Printer(); } } } namespace CenterA.Technology { public class DepartmentA { internal Printer PrinterA { get; set; } public DepartmentA() { PrinterA = new Printer(); } public void PrintSomething() { if (PrinterA.IsBad) { CenterA.Administration.DepartmentB departmentB = new CenterA.Administration.DepartmentB(); departmentB.PrinterB.Print(); } else { PrinterA.Print(); } } } }
上面可以看见,DepartmentB和DepartmentA虽然不在同一个命名空间,但由于同属于CenterA这个程序集,DepartmentA是可以访问DepartmentB中的Printer 的。
interface 的作用
首先接口具有约束作用,可以限定类必须实现某些功能,再次,规范多个开发人员的代码(使用同样的方法名称),
让它们从一个接口继承,方便管理统一,方便调用.如果不使用接口,虽然可以达到目的.但是没有约束,将来软件很容易形成Bug或者漏掉这些方法.
在定义接口时候要注意如下几点:
1.接口声明不能包含以下成员:
数据成员、静态成员。
2.接口声明只能包含如下类型的非静态成员函数的声明:
方法、属性、事件、索引器。
3.这些函数成员的声明不能包含任何实现代码,而在每一个成员声明的主体后必须使用分。
继承"基类"跟继承"接口"都能实现某些相同的功能,但有些接口能够完成的功能是只用基类无法实现的
1.接口用于描述一组类的公共方法/公共属性. 它不实现任何的方法或属性,只是告诉继承它的类至少要实现哪些功能,继承它的类可以增加自己的方法.
2.使用接口可以使继承它的类: 命名统一/规范,易于维护.比如: 两个类 "狗"和"猫",如果它们都继承了接口"动物",其中动物里面有个方法Behavior(),那么狗和猫必须得实现Behavior()方法,
并且都命名为Behavior这样就不会出现命名太杂乱的现象.如果命名不是Behavior(),接口会约束即不按接口约束命名编译不会通过.
3.提供永远的接口。 当类增加时,现有接口方法能够满足继承类中的大多数方法,没必要重新给新类设计一组方法,也节省了代码,提高了开发效率.
C# 接口的作用浅谈举例:https://blog.youkuaiyun.com/liuqinghui1990/article/details/77171051
Partial:
1. 什么是局部类型?
C# 2.0 引入了局部类型的概念。局部类型允许我们将一个类、结构或接口分成几个部分,分别实现在几个不同的.cs文件中。
局部类型适用于以下情况:
(1) 类型特别大,不宜放在一个文件中实现。
(2) 一个类型中的一部分代码为自动化工具生成的代码,不宜与我们自己编写的代码混合在一起。
(3) 需要多人合作编写一个类。
局部类型是一个纯语言层的编译处理,不影响任何执行机制——事实上C#编译器在编译的时候仍会将各个部分的局部类型合并成一个完整的类。
public partial class Program
{
static void Main(string[] args)
{
}
}
partial class Program
{
public void Test()
{
}
}
2. 局部类型的限制
(1) 局部类型只适用于类、接口、结构,不支持委托和枚举。
(2) 同一个类型的各个部分必须都有修饰符 partial。
(3) 使用局部类型时,一个类型的各个部分必须位于相同的命名空间中。
(4) 一个类型的各个部分必须被同时编译。
3. 局部类型的注意点
(1) 关键字partial是一个上下文关键字,只有和 class、struct、interface 放在一起时才有关键字的含义。因此partial的引入不会影响现有代码中名称为partial的变量。
(2) 局部类型的各个部分一般是分开放在几个不同的.cs文件中,但C#编译器允许我们将他们放在同一文件中。
4. 局部类型的应用特性
在局部类型上的特性具有“累加”效应。
[Attribute1, Attribute2("Hello")]
partial class Class1{}
[Attribute3, Attribute2("Exit")]
partial class Class1{}
相当于
[Attribute1, Attribute2("Hello"), Attribute3, Attribute2("Exit")]
class Class1 {}
注:Attribute2属性允许在类上多次使用。
5. 局部类型上的修饰符
(1) 一个类型的各个部分上的访问修饰符必须维持一致性。
(2) 如果一个部分类使用了abstract修饰符,那么整个类都将被视为抽象类。
(3) 如果一个部分类使用了 sealed 修饰符,那么整个类都将被视为密封类。
(4) 一个类的各个部分不能使用相互矛盾的修饰符,比如不能在一个部分上使用abstract,又在另一个部分上使用sealed。
(5)如果一个部分类使用了 static修饰符,那么整个类都将被视为静态类。
6. 局部类型的基类和接口
(1) 一个类型的各个部分上指定的基类必须一致。某个部分可以不指定基类,但如果指定,则必须相同。
(2) 局部类型上的接口具有“累加”效应。
partial class Class2: Iinterface1, Iinterface2 {}
partial class Class2: Iinterface3 {}
partial class Class2: Iinterface2 {}
相当于
class Class2: Iinterface1, Iinterface2, Iinterface3 {}
params表示参数是可变个数的
在不加params修饰时,只能往里传入一个数组。
在加了params后,不仅可以传入数组,还可以传入多个参数,这多个参数等价为数组。
在方法声明中的 params 关键字之后不允许任何其他参数,并且在方法声明中只允许一个 params 关键字。
C++中允许函数带有默认参数,允许不定数目的参数。但C#中不允许函数参数带有默认值。默认值这种功能,只能用函数重载来代替实现了。
但是C#允许方法带有不定数量的参数。使用params关键字,且参数必须是参数表中的最后一个参数。
要点:
1params不能用在多维数组上,这个大家可以试试,肯定会报错的。错误提示参数数组必须为一维数组
2.不能使用params来重载方法
3.不允许ref和out 与 params同时使用,加上之后程序编译时报错。因为数组的传递的本来就是引用(只限于用数组的方式进行调用),用上面调用方式2则无法对实参进行更改。用调用方式1时,在函数内部对参数数组的值进行了更改,则作为实参的数组的值也被更改。
4.params数组必须是最后一个参数。
5.一个没有params的方法的优先级高于带有params的方法
6.可以不传入任何参数
System.Object(object)是所有类的根,使用params object数组来声明一个方法,它能接受任意数量的object参数;换言之,不仅参数的数量是任意的,参数的类型也可以是任意的。所以,此方法称为Black.Hole(黑洞)
1. 可以不向它传递任何参数
2. 可以在调用它时,传递null作为参数
3. 可以向它传递一个实际的数组。也就是说,可以人工创建本由编译器来创建的数组
4. 可以向它传递不同类型的其他任何参数,这些参数将自动封装到一个object数组
using System;
class Program
{
static void Main()
{
fun("Call 1");
Console.WriteLine("\n");
fun("Call 2", 2);
Console.WriteLine("\n");
fun("Call 3", 3.2, "hey look at here", false);
Console.ReadLine();
}
static void fun(string str, params object[] args)
{
Console.WriteLine(str);
foreach (object ob in args)
{
if (ob is int)
{
Console.Write(" int : " + ob);
}
else if (ob is double)
{
Console.Write(" double : " + ob);
}
else if (ob is string)
{
Console.Write(" string : " + ob);
}
else
{
Console.Write(" other type : " + ob);
}
}
}
}
using System;
public class MyClass
{
public static void UseParams(params int[] list)
{
for (int i = 0 ; i < list.Length; i++)
{
Console.WriteLine(list[i]);
}
Console.WriteLine();
}
public static void UseParams2(params object[] list)
{
for (int i = 0 ; i < list.Length; i++)
{
Console.WriteLine(list[i]);
}
Console.WriteLine();
}
static void Main()
{
UseParams(1, 2, 3);
UseParams2(1, 'a', "test");
// An array of objects can also be passed, as long as
// the array type matches the method being called.
int[] myarray = new int[3] {10,11,12};
UseParams(myarray);
}
}