一、base关键字
可以通过base关键字访问上一级父类方法的访问。静态static函数无法调用base
二、new 关键字new
new有2个作用。
new运算符 用来分配内存空间和初始化对象。
new修饰符 微软官方说明:可以显式隐藏从基类继承的成员,该成员的派生版本将替换基类版本。(以前一直不在乎这种场合,因为编译器就提示警告)他和overrider有什么区别呢?
/// <summary> /// 关键字 /// </summary> public class KeywordA { public KeywordA() { Name = "apple"; } public string Name { get; set; } public virtual void Test() { Console.WriteLine("KeywordA.Test()" + Name); } public void Test1() { Console.WriteLine("KeywordA.Test1()" + Name); } } public class KeywordB : KeywordA { public KeywordB() { Name = "button"; } public new string Name { get; set; } public new void Test() { Console.WriteLine("KeywordB.Test()" + Name); base.Test(); } public new void Test1() { Console.WriteLine("KeywordB.Test1()" + Name); base.Test1(); } } public class KeywordC : KeywordA { public KeywordC() { Name = "city"; } public override void Test() { Console.WriteLine("KeywordC.Test()"+Name); base.Test(); } }
static void Main(string[] args) { KeywordA a = new KeywordA(); a.Test(); a.Test1(); Console.WriteLine("========================="); KeywordB b = new KeywordB(); b.Test(); b.Test1(); a = new KeywordB(); a.Test(); a.Test1(); Console.WriteLine("========================="); KeywordC c = new KeywordC(); c.Test(); a = new KeywordC(); a.Test(); a.Test1(); Console.WriteLine("========================="); Console.ReadKey(); }
通过以上代码执行得出以下结果:
KeywordA.Test()apple
KeywordA.Test1()apple
=========================
KeywordB.Test()button
KeywordA.Test()apple
KeywordB.Test1()button
KeywordA.Test1()apple
KeywordA.Test()apple
KeywordA.Test1()apple
=========================
KeywordC.Test()city
KeywordA.Test()city
KeywordC.Test()city
KeywordA.Test()city
KeywordA.Test1()city
=========================
1.new 只是个修饰符,看IL代码和不加NEW的方法一样。都叫覆盖。为了减少warn,建议加把。
2.虚方法、实方法都可以被覆盖(new),抽象方法,接口 不可以。
3.不管是重写还是覆盖都不会影响父类自身的功能。
4.当用子类创建父类的时候,如 A c = new B(),重写会调用子类的功能;而覆盖不会,仍然调用父类功能。
三、abstract 修饰符 abstract关键字
关于virtual 虚方法和抽象方法 、抽象类网上资料太多了。也非常好区分。abstract继承类必须实现抽象定义。而virtual 可以选择是否重写基类虚方法。 这里就摘录微软的定义了:
-
abstract 修饰符指示所修饰的内容缺少实现或未完全实现。
-
abstract 修饰符可用于类、方法、属性、索引器和事件。
-
标记为抽象或包含在抽象类中的成员必须通过从抽象类派生的类来实现。
抽象类具有以下特性:
-
抽象类不能实例化。
-
抽象类可以包含抽象方法和抽象访问器。
-
不能用 sealed(C# 参考) 修饰符修饰抽象类,因为这两个修饰符的含义是相反的。 采用 sealed 修饰符的类无法继承,而 abstract 修饰符要求对类进行继承。
-
从抽象类派生的非抽象类必须包括继承的所有抽象方法和抽象访问器的实际实现。
四、virtual 修饰符
virtual 关键字用于修饰方法、属性、索引器或事件声明,并使它们可以在派生类中被重写。
除了声明和调用语法不同外,虚拟属性的行为与抽象方法一样。
-
在静态属性上使用 virtual 修饰符是错误的。
-
通过包括使用 override 修饰符的属性声明,可在派生类中重写虚拟继承属性。
五、const / readonly
都是只读的意思。表面上看区别在于const首次赋值后就不可以改了,而readonly在初始化类后就不能修改了。
public class Keyword2 { public static string Name_static = "Name_static"; public readonly string Name_readonly = "Name_readonly"; public const string Name_const = "Name_const"; public static readonly string Name_static_readonly = "Name_static_readonly"; public Keyword2(string text="Init") { Name_static = "Name_static_" + text; Name_readonly = "Name_readonly_" + text; //Name_const = "Name_const" + text; //Name_static_readonly = "Name_static_readonly" + text; } }
static void Main(string[] args) { //DoKeyword1(); DoKeyword2(); Console.ReadKey(); } /// <summary> /// const readonly /// </summary> private static void DoKeyword2() { Console.WriteLine("========初始化字段================="); Console.WriteLine(Keyword2.Name_const); Console.WriteLine(Keyword2.Name_static); Console.WriteLine(Keyword2.Name_static_readonly); Console.WriteLine("========初始化构造函数================="); Keyword2 kw = new Keyword2(); Console.WriteLine(kw.Name_readonly); Console.WriteLine(Keyword2.Name_const); Console.WriteLine(Keyword2.Name_static); Console.WriteLine(Keyword2.Name_static_readonly); Console.WriteLine("========修改内容================="); //Keyword2.Name_const = Keyword2.Name_const+"_Edit"; //Keyword2.Name_static_readonly = Keyword2.Name_static_readonly + "_Edit"; //kw.Name_readonly = Keyword2.Name_readonly + "_Edit"; Keyword2.Name_static = Keyword2.Name_static + "_Edit"; Console.WriteLine(Keyword2.Name_static); }
IL代码:
.class public auto ansi beforefieldinit 关键字使用和执行顺序.Keyword2 extends [mscorlib]System.Object { // Fields .field public static literal string Name_const = "Name_const" .field public static string Name_static .field public initonly string Name_readonly .field public static initonly string Name_static_readonly // Methods .method public hidebysig specialname rtspecialname instance void .ctor ( [opt] string text ) cil managed { .param [1] = "Init" // Method begins at RVA 0x2156 // Code size 54 (0x36) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldstr "Name_readonly" IL_0006: stfld string 关键字使用和执行顺序.Keyword2::Name_readonly IL_000b: ldarg.0 IL_000c: call instance void [mscorlib]System.Object::.ctor() IL_0011: nop IL_0012: nop IL_0013: ldstr "Name_static_" IL_0018: ldarg.1 IL_0019: call string [mscorlib]System.String::Concat(string, string) IL_001e: stsfld string 关键字使用和执行顺序.Keyword2::Name_static IL_0023: ldarg.0 IL_0024: ldstr "Name_readonly_" IL_0029: ldarg.1 IL_002a: call string [mscorlib]System.String::Concat(string, string) IL_002f: stfld string 关键字使用和执行顺序.Keyword2::Name_readonly IL_0034: nop IL_0035: ret } // end of method Keyword2::.ctor .method private hidebysig specialname rtspecialname static void .cctor () cil managed { // Method begins at RVA 0x218d // Code size 21 (0x15) .maxstack 8 IL_0000: ldstr "Name_static" IL_0005: stsfld string 关键字使用和执行顺序.Keyword2::Name_static IL_000a: ldstr "Name_static_readonly" IL_000f: stsfld string 关键字使用和执行顺序.Keyword2::Name_static_readonly IL_0014: ret } // end of method Keyword2::.cctor } // end of class 关键字使用和执行顺序.Keyword2
总结:
1.const 它也是静态的。
2.Name_readonly 在编译时自动将赋值写入构造函数,完成初始化值。
3.初始化顺序是 先 const-> static ->构造函数。
六、volatile 修饰符
七、in/out 泛型修饰符
说得直白点就是泛型类型的类型转换。
out “子类”向“父类”转换,即泛型接口的协变
in “父类”向“子类”转换,即泛型接口的逆变
目前我已知的类库中 泛型枚举接口 ,还有泛型委托都已经改成协变逆变了。
public interface IEnumerable<out T> : IEnumerable
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
public delegate void Action<in T>(T obj);
案例1 IEnumerable<out T>使用
public static void 泛型类型转换() { // 普通的类型转换 Animal animal = new Dog(); //类型的隐式转换 Dog dog = (Dog)animal; //类型的强制转换 // 泛型的类型转换 IEnumerable<Animal> iAnimal = null; IEnumerable<Dog> iDog = null; //// “子类”向“父类”转换,即泛型接口的协变 out iAnimal = iDog; // “父类”向“子类”转换,即泛型接口的逆变 in , //由于IEnumerable没有提供IEnumerable<in T>,所以编译时出错 iDog = iAnimal; } public class Animal { } public class Dog : Animal { }
案例2 实际场景中遇到的协变
一般场景,用协变的机会会多点。
有2个继承关系的类。
基类是 RoomInfoVModel房间信息相关的VO1.
它的子类RoomInfoPriceConvertVModel用来计算价格的VO2.
因为VO2主要是参与价格计算最终生成实际价格会放入到VO1,我不想让VO1太过臃肿。
那么在以下方法中,我从DB中获取到VO2List如何返回VO1List
我原先用了方法2:遍历集合吧对象逐个进行类型转换。
现在换成方法1了。IList<> 没有提供in out 功能,又懒得去扩展ILIST。
所以就使用 public delegate TOutput Converter<in TInput, out TOutput>(TInput input);转换了。
/// <summary> /// 根据入住日期获取房型价格列表 /// </summary> /// <param name="req"></param> /// <returns></returns> public IList<RoomInfoVModel> GetCustomerRoomPrices(HashRequest req) { var discountName = req.GetString("discoutname"); var bIsMustExistsRoom = req.GetBoolean("IsMustExistsRoom"); var inDate = req.GetString("inDate"); var exitDate = req.GetString("ExitDate"); Sql sql = new Sql(); sql.Append(""); var models = Repo.Fetch<RoomInfoPriceConvertVModel>(sql.SQL, sql.Arguments); ConvertPrices(discountName, inDate, models); // 1、通过逆变 return models.ConvertAll<RoomInfoVModel>((input) => input); // 2.遍历集合吧对象逐个进行类型转换 //return models.Select(a => a as CustomerHotelInfoVModel).ToList(); // 3.对象属性自动映射 //return AutoMapper.Mapper.Map<List<CustomerHotelInfoVModel>>(models); }
最后,其他修饰符会在后期文章中结合具体内容出现,比如async , params。