• C#object


    OBJECT类型
    object(System.Object)是所有类型的终极父类,所有类型都可以向上转换为object。
    下面我们看一个例子
    publicclass Stack { int position; object[] data = newobject[10]; publicvoid Push (objectobj) { data[position++] = obj; } publicobjectPop() { return data[--position]; } }
    这是一个后进先出的这么一个栈,因为是object类型,所以你可以Push和Pop任意的类型到这个栈里
    Stack stack = new Stack(); stack.Push ("sausage"); string s = (string) stack.Pop(); // 向下转换,显式的转换一下 Console.WriteLine (s); // sausage
    object是引用类型,但值类型可以转换为object,反之亦然。(类型统一)
    stack.Push (3); int three = (int) stack.Pop();
    在值类型和object之间转换的时候,CLR必须执行一些特殊的工作,以弥补值类型和引用类型之间语义上的差异,这个过程就叫做装箱和拆箱。
     
    装箱(boxing)
    装箱就是把值类型的实例转换为引用类型的实例的动作,目标引用类型可以是object,也可以是某个接口
    int x = 9; object obj = x; // 把int值装箱
     
    拆箱(unboxing)
    拆箱正好和装箱相反,把对象转换为原来的值类型
    int y = (int)obj; // 还原int值
    拆箱需要显式的转换
    拆箱过程中,运行时会检查这个值类型和object对象的真实类型是否匹配,如果不匹配就抛出InvalidCastException
    object obj = 9; // 9 在这里是int类型long x = (long) obj; // InvalidCastException
    下面的转换就是可以的,int类型可以隐式的转换为long类型,但是像上面的直接拆箱就不可以:
    object obj = 9; long x = (int) obj;
    装箱对于类型统一是非常重要的,但是系统设计还是不够完美,比如数组和泛型只支持引用转换,不支持装箱
    object[] a1 = newstring[3]; // 可以的object[] a2 = newint[3]; // 会报错
     
    装箱拆箱的复制
    • 装箱会把值类型的实例复制到一个新的对象
    • 拆箱会把这个对象的内容再复制给一个值类型的实例
    看个例子:
    int i = 3; object boxed = i; i = 5; Console.WriteLine (boxed); // 3
     
    静态和运行时类型检查
    C#的程序既会做静态的类型检查(编译时),也会做运行时的类型检查(CLR)
    静态检查:就是不运行程序的情况下,让编译器保证你的程序的正确性,比如 int x = "5"; 这么写肯定是不行的
    运行时的类型检查由CLR执行,发生在向下的引用转换或拆箱的时候。
    object y = "5"; int z = (int) y; // 运行时报错,向下转换失败
    运行时检查之所以可行是因为每个在heap上的对象内部都存储了一个类型token。这个token可以通过调用object的GetType()方法来获取。
     
    GetType方法与typeof操作符
    所有C#的类型在运行时都是以System.Type的实例来展现的
    有两种方式可以获得System.Type对象:一是在实例上调用GetType()方法;第二个是在类型名上使用typeof操作符。
    GetType是在运行时被算出的,typeof是在编译时被算出的(静态)(当涉及到泛型类型参数时,它是由JIT编译器来解析的)
     
    System.Type
    System.Type的属性有:类型名称、Assembly、基类等等。直接看例子:
    using System; publicclass Point { publicint X, Y; } class Test { staticvoid Main() { Point p = new Point(); Console.WriteLine (p.GetType().Name); // Point Console.WriteLine (typeof (Point).Name); // Point Console.WriteLine (p.GetType() == typeof(Point)); // True Console.WriteLine (p.X.GetType().Name); // Int32 Console.WriteLine (p.Y.GetType().FullName); // System.Int32 } }
     
    ToString方法
    ToString()方法会返回一个类型实例的默认文本表示
    所有的内置类型都重写了该方法
    int x = 1; string s = x.ToString(); // s is "1"
    我们可以在自定义的类型上重写ToString()方法,如果你没有重写该方法,那么就会返回该类的名称,是一个包括命名空间的全名
    publicclass Panda { publicstring Name; publicoverridestring ToString() => Name; } ... Panda p = new Panda { Name = "Petey" }; Console.WriteLine (p); // Petey
    当你调用一个被重写的object成员的时候,例如在值类型上直接调用ToString()方法,这时候就不会发生装箱操作,但是如果你进行了转换,那么装箱操作就会发生
    int x = 1; string s1 = x.ToString(); // 这里就没有发生装箱object box = x; string s2 = box.ToString(); // 调用的就是装箱后的值

     
    这篇文章主要来讲讲c#中的泛型,因为泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用。
    一、什么是泛型?
    泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个非常重要的新功能。
    我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的,可以看出,微软还是很贴心的。
    二、为什么要使用泛型?
    接下来我们来看一段代码。
    publicclass GenericClass { publicvoid ShowInt(int n) { Console.WriteLine("ShowInt print {0},ShowInt Parament Type Is {1}",n,n.GetType()); } publicvoid ShowDateTime(DateTime dt) { Console.WriteLine("ShowDateTime print {0},ShowDateTime Parament Type Is {1}", dt, dt.GetType()); } publicvoid ShowPeople(People people) { Console.WriteLine("ShowPeople print {0},ShowPeople Parament Type Is {1}", people, people.GetType()); } }
    staticvoid Main(string[] args) { GenericClass generice = new GenericClass(); generice.ShowInt(11); generice.ShowDateTime(DateTime.Now); generice.ShowPeople(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }
     
    显示结果:
     我们可以看出这三个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.1版的时候,还没有泛型这个概念,那么怎么办呢。就有人想到了OOP三大特性之一的继承,我们知道,C#语言中,所有类型都源自同一个类型,那就是object。
    publicclass GenericClass { publicvoid ShowObj(object obj) { Console.WriteLine("ShowObj print {0},ShowObj Parament Type Is {1}", obj, obj.GetType()); } } staticvoid Main(string[] args) { Console.WriteLine("*****************object调用*********************"); generice.ShowObj(11); generice.ShowObj(DateTime.Now); generice.ShowObj(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }
    显示结果:
    我们可以看出,目地是达到了。解决了代码的可读性,但是这样又有个不好的地方了,我们这样做实际上是一个装箱拆箱操作,会损耗性能。
    终于,微软在2.0的时候发布了泛型。接下来我们用泛型方法来实现该功能。
    三、泛型类型参数
    在使用泛型方法之前,我们先来了解下有关于泛型的一些知识。
    在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定的特定类型的占位符。 泛型类( GenericList<T>)无法按原样使用,因为它不是真正的类型;它更像是类型的蓝图。 若要使用 GenericList<T>,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。 此特定类的类型参数可以是编译器可识别的任何类型。 可创建任意数量的构造类型实例,其中每个使用不同的类型参数,如下所示:
    GenericList<float> list1 = new GenericList<float>(); GenericList<ExampleClass> list2 = new GenericList<ExampleClass>(); GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
    在 GenericList<T> 的每个实例中,类中出现的每个 T 在运行时均会被替换为类型参数。 通过这种替换,我们已通过使用单个类定义创建了三个单独的类型安全的有效对象。 
    三、泛型约束
    定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的几种类型施加限制。 如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。 这些限制称为约束。 通过使用 where 上下文关键字指定约束。 下表列出了六种类型的约束:
    where T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。)
    class MyClass<U> where U : struct///约束U参数必须为“值 类型” { } publicvoid MyMetod<T>(T t) where T : struct { }
    where T:类(类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。)
    class MyClass<U> where U : class///约束U参数必须为“引用类型” { } publicvoid MyMetod<T>(T t) where T : class { }
    where T:new()(类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。)
    class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new() { // ... }
    where T:<基类名>(类型参数必须是指定的基类或派生自指定的基类。)
    publicclass Employee{} publicclass GenericList<T> where T : Employee
    where T:<接口名称>(类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。)
    ///<summary>/// 接口 ///</summary>interface IMyInterface { } ///<summary>/// 定义的一个字典类型 ///</summary>///<typeparam name="TKey"></typeparam>///<typeparam name="TVal"></typeparam>class Dictionary<TKey, TVal> where TKey : IComparable, IEnumerable where TVal : IMyInterface { publicvoid Add(TKey key, TVal val) { } }
    where T:U(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。也就是说T和U的参数必须一样)
    class List<T> { void Add<U>(List<U> items) where U : T {/*...*/} }
    以上就是对六种泛型的简单示例,当然泛型约束不仅仅适用于类,接口,对于泛型方法,泛型委托都同样适用。
    三、泛型方法
    publicclass GenericClass { publicvoid ShowT<T>(T t) { Console.WriteLine("ShowT print {0},ShowT Parament Type Is {1}", t, t.GetType()); } } staticvoid Main(string[] args) { Console.WriteLine("*****************泛型方法调用*********************"); generice.ShowT<int>(11); generice.ShowT<DateTime>(DateTime.Now); generice.ShowT<People>(new People { Id = 11, Name = "Tom" }); Console.ReadKey(); }
    显示结果:
    也是一样的,现在终于实现了我们想要达到的效果了。我们可以看出,无论是什么方式调用,最后我们获取出来的类型都是原始类型。我们知道,用object获取是利用了继承这一特性,当编译器编译的时候,我们传入的参数会进行装箱操作,当我们获取的时候又要进行拆箱操作,这个方法会损耗性能 。那么泛型方法实现的原理又是怎样的呢?首先,我们要知道,泛型是一个语法糖,在我们调用泛型方法,编译器进行编译时,才会确定传入的参数的类型,从而生成副本方法。这个副本方法与原始方法一法,所以不会有装箱拆箱操作,也就没有损耗性能这回事了。
    四、泛型类
    泛型类封装不特定于特定数据类型的操作。
    通常,创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。
    创建自己的泛型类时,需要考虑以下重要注意事项:
    • 要将哪些类型泛化为类型参数。
                   通常,可参数化的类型越多,代码就越灵活、其可重用性就越高。 但过度泛化会造成其他开发人员难以阅读或理解代码。
    • 要将何种约束(如有)应用到类型参数
     
             其中一个有用的规则是,应用最大程度的约束,同时仍可处理必须处理的类型。 例如,如果知道泛型类仅用于引用类型,则请应用类约束。 这可防止将类意外用于值类型,并     使你可在   T  上使用  as  运算符和检查 null 值。      
     
    • 是否将泛型行为分解为基类和子类。
    因为泛型类可用作基类,所以非泛型类的相同设计注意事项在此也适用。 请参阅本主题后文有关从泛型基类继承的规则。
    • 实现一个泛型接口还是多个泛型接口。
    class BaseNode { } class BaseNodeGeneric<T> { } // concrete typeclass NodeConcrete<T> : BaseNode { } //closed constructed typeclass NodeClosed<T> : BaseNodeGeneric<int> { } //open constructed type class NodeOpen<T> : BaseNodeGeneric<T> { }
    五、泛型接口
    • 定义一个泛型接口:
    interface IMyGenericInterface<T> { }
    • 一个接口可定义多个类型参数,如下所示:
    interface IMyGenericInterface<TKey,TValue> { }
    • 具体类可实现封闭式构造接口,如下所示:
    interface IBaseInterface<T> { } class SampleClass : IBaseInterface<string> { }//如果T有约束,那么string类型必须得满足T的约束
    六、泛型委托
    委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样,如以下示例中所示:
    class Program { staticvoid Main(string[] args) { Del<int> m1 = new Del<int>(Notify); m1.Invoke(1111); Del<string> m2 = new Del<string>(Notify); m2.Invoke("字符串"); Console.ReadKey(); } publicdelegatevoid Del<T>(T item); publicstaticvoid Notify(int i) { Console.WriteLine("{0} type is {1}", i,i.GetType()); } publicstaticvoid Notify(string str) { Console.WriteLine("{0} type is {1}", str, str.GetType()); } }
    运行结果:
     
    七、泛型代码中的默认关键字:Default
    在泛型类和泛型方法中产生的一个问题是,在预先未知以下情况时,如何将默认值分配给参数化类型 T:
    • T 是引用类型还是值类型。
    • 如果 T 为值类型,则它是数值还是结构。
    给定参数化类型 T 的一个变量 t,只有当 T 为引用类型时,语句 t = null 才有效;只有当 T 为数值类型而不是结构时,语句 t = 0 才能正常使用。解决方案是使用 default 关键字,此关键字对于引用类型会返回空,对于数值类型会返回零。对于结构,此关键字将返回初始化为零或空的每个结构成员,具体取决于这些结构是值类型还是引用类型。
    namespace MyGeneric { class Program { staticvoid Main(string[] args) { object obj1=GenericToDefault<string>(); object obj2 = GenericToDefault<int>(); object obj3 = GenericToDefault<StructDemo>(); Console.ReadKey(); } publicstatic T GenericToDefault<T>() { returndefault(T); } } publicstruct StructDemo { publicint Id { get; set; } publicstring Name { get; set; } } }
    运行结果:
  • 相关阅读:
    面条代码 vs. 馄沌代码
    GraphQL 到底怎么用?看看这个例子就知道了
    程序员难逃二八法则,如何晋升为头部 20% 玩家?
    正则匹配负正数和负小数
    js、Jquery处理自动计算的输入框事件
    mobile easyui兼容实体数据(tree插件为例)
    framework7中一行的字如果过多就省略号显示的CSS写法
    PHP获取系统时间不对的解决办法(转载)
    BZOJ 3156: 防御准备
    P4098 [HEOI2013]ALO
  • 原文地址:https://www.cnblogs.com/sailing92/p/14393269.html
Copyright © 2020-2023  润新知