《C#入门经典》学习笔记(泛型)

来源:互联网 发布:mac可以玩的网络游戏 编辑:程序博客网 时间:2024/04/29 03:04

泛型

 

泛型的概念

使用ArrayList类时,任何类型的对象都可以存储在ArrayList中
但很多时候需要制定集合中的类型,使用自定义的集合类可以解决
而更好的方法就是使用泛型

泛型类是以实例化过程中提供的类型或类为基础建立的,可以对对象进行强类型化
例如:
一般集合的定义:
CollectionClass col = new CollectionClass();
创建ItemClass类型对象的集合:
CollectionClass<ItemClass> col = new CollectionClass<ItemClass>();
尖括号语法就是把变量类型传送给泛型类型的方式

泛型非常适合于集合
泛型并不限于类,还可以创建泛型接口、泛型方法,甚至泛型委托

通常,创建类时会编译为一个指定的类型,再使用
而在创建泛型类时,.NET运行库允许在需要时才动态生成泛型类
泛型允许灵活地创建类型,处理一种或多种特定类型的对象,而类型在实例化时才确定

 

使用泛型


可空类型

值类型与引用类型的区别:值类型必须包含一个值,它们可以在声明之后、赋值之前,在未

赋值的状态下存在,但不能以任何方式使用。而引用类型可以是null。

泛型使用System.Nullable<T>类型提供了使值类型为空的一种方式
例如:
System.Nullable<int> nullableInt;

声明了一个变量nullableInt,它可以拥有int变量能包含的任意值或者值null
初始化:
nullableInt = null;
或者:
nullableInt = new System.Nullable<int>();

必须在初始化后才能使用它

可以像测试引用类型一样,测试可空类型是否为null:
if (nullableInt == null)
{
}

或则使用HasValue属性,指示当前的Nullable对象是否有值:
if (nullableInt.HasValue)
{
}

不适用于引用类型,因为值为null的对象是不存在的对象,是不能访问的

C#提供了更方便的语法来使用可空类型
例如:
int? nullableInt;
int? 是System.Nullable<int>的缩写

1 运算符和可空类型
包含在可空类型中的值会隐式转换为需要的类型,使用适当的运算符
这也适用于结构和自己提供的运算符
例如:
int? op1 = 5;
int? result = op1 * 2;
注意result也是int?类型
下面编译会出错:
int? op1 = 5;
int result = op1 * 2;
因为int?和int严格上并不是同样的类型
如果显式转换:
int? op1 = 5;
int result = (int)op1 * 2;
只要op1有一个值,就可以正常运行,如果op1是null,就会生成异常

当有运算等式中有其中一个或两个操作数是null时:
对于除了bool?之外的所有简单可空类型,该操作的结果是null
对于结构,可以定义自己的运算符来处理这种情况
对于bool?,为&和 | 定义的运算符会得到非空返回值
bool?运算结果
op1 op2 op1 & op2 op1 | op2
true true true  true
true false false  true
true null null  true
false true false  true
false false false  false
false null false  null
null true null  true
null false false  null
null null null  null

2 ??运算符
??运算符允许提供可空类型是null和不是null时的默认值
例如:
int? op1 = null;
int result = op1 * 2 ?? 5;
当??运算符检测到前面运算的结果op1 * 2是null时,把5赋予result
注意:在结果中放入int类型的变量result时,??运算符会自动进行显式转换

也可以把??等式的结果放在int?中:
int? result = op1 * 2 ?? 5;

例如:
public class Vector
{
    public int? R = null;
    public int? T = null;

    public Vector(int? r, int? t)
    {
        R = r;
        T = t;
    }

    public static Vector operator +(Vector op1, Vector op2)
    {
        try
        {
            return new Vector((int)(op1.R + op2.R), (int)(op1.T + op2.T));
        }
        catch
        {
            return new Vector(null, null);
        }
    }

    public static Vector operator -(Vector op1, Vector op2)
    {
        return new Vector(op1.R - op2.R ?? 0, op1.T - op2.T ?? 0);
    }

    public override string ToString()
    {
        string rString = R.HasValue ? R.ToString() : "null";
        string tString = T.HasValue ? T.ToString() : "null";
        return string.Format("({0}, {1})", rString, tString);
    }

}

执行:
Vector v1 = new Vector(12, 25);
Vector v2 = new Vector(null, 31);
string rString1 = v1 + "+" + v2 + "=" + (v1 + v2);
string rString2 = v1 + "-" + v2 + "=" + (v1 - v2);

结果:
rString1是(12, 25)+(null, 31)=(null, null)
rString2是(12, 25)-(null, 31)=(0, -6)

说明:
R和T的类型都是int?,所以它们可以为空
先是+运算符的定义,由于用了强制转换,当R或T有null值时会抛出异常
再来是-运算符的定义,使用??运算符,当R或T有null值时使用默认值0
重写ToString(),使用易于理解的字符串表达方式显示


System.Collections.Generic命名空间

System.Collections.Generic命名空间包含用于处理集合的泛型类型
泛型类型包括
类型   说明
List<T>   T类型对象的集合
Dictionary<K, V> V类型的项与K类型的键值相关的集合

1 List<T>
使用这个泛型的集合类型会更快捷、更简单实现需要的方法
创建T类型对象的集合:
List<T> myCollection = new List<T>();

实例化的对象支持的方法和属性(T是提供给List<T>泛型的类型)
成员    说明
int Count   该属性给出集合中项的个数
void Add(T item)  把item添加到集合中
void AddRange(IEnumerable<T>) 把多个项添加到集合中
IList<T> AsReadOnly()  给集合返回一个只读接口
int Capacity   获取或设置集合可以包含的项数
void Clear()   删除集合中的所有项
bool Contains(T item)  确定item是否包含在集合中
int IndexOf(T item)  获取item的索引(-1是没找到)
void Insert(int index, T item) 把item插入到集合的指定索引上
bool Remove(T item)  从集合中删除第一个item
void RemoveAt(int index) 从集合中删除索引index处的项
void CopyTo(T[] array, int index) 把集合中的项复制到数组array中
IEnumerator<T> GetEnumerator() 获取一个IEnumerator<T>实例,用于迭代集合
 
List<T>有一个Item属性,用于索引方式访问访问

例如:
class MyClass
{
    public int Num;
    public MyClass(int i)
    {
        Num = i;
    }
}

使用List<T>:
List<MyClass> myCollection = new List<MyClass>();
myCollection.Add(new MyClass(3));
myCollection.Add(new MyClass(7));

string rString1 = "";
foreach (MyClass myClass in myCollection)
{
    rString1 += myClass.Num.ToString();
}
string rString2 = myCollection.Count.ToString();

结果:
rString1是37
rString2是2

说明:
这里使用了自定义的类来创建泛型集合,也可以使用默认的数据类型

使用CollectionBase能更多地控制向类的成员,但一般应选择更方便快捷的List<T>

2 对泛型列表进行排序和搜索
使用IComparer和IComparable接口可以比较两个对象
泛型接口IComparer<T>和IComparable<T>有相同的作用

泛型方法
非泛型方法
区别

int IComparable<T>.CompareTo(T otherObj)
int IComparable.CompareTo( object, otherObj)
泛型版本中是强类型化的

bool IComparable<T>.Equals(T otherObj)
N/A
在非泛型接口中不存在,可以使用object.Equals()替代

int IComparer<T>.Compare(T objectA, T objectB)
int IComparer.Compare(object objectA, object objectB)
泛型版本中是强类型化的

bool IComparer<T>.Equals(T objectA, T objectB)
N/A
在非泛型接口中不存在,可以使用object.Equals()替代

int IComparer<T>.GetHashCode(T objectA)
N/A
在非泛型接口中不存在,可以使用object. GetHashCode()替代

要对List<T>排序,可以在要排序的类型上提供IComparable<T>或IComparer<T>接口
还可以提供泛型委托,作为排序方法
泛型委托 作用  格式
Comparison<T> 用于排序方法 int method (T objectA, T objectB)
Predicate<T> 用于搜索方法 bool method (T targetObject)

例如:
定义排序和搜索方法:
public static int Compare(int x, int y)
{
    if (x > y)
    {
        return 1;
    }
    else if (x < y)
    {
        return -1;
    }
    return 0;
}

public static bool Search(int target)
{
    if (target > 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

执行:
string rString1 = "";
string rString2 = "";
string rString3 = "";

List<int> route = new List<int>();
route.Add(5);
route.Add(-7);
route.Add(0);
route.Add(3);

foreach (int i in route)
{
    rString1 += i + ";";
}

Comparison<int> sorter = new Comparison<int>(Compare);
route.Sort(sorter);
foreach (int i in route)
{
    rString2 += i + ";";
}

Predicate<int> searcher = new Predicate<int>(Search);
List<int> routeSearch = route.FindAll(searcher);
foreach (int i in routeSearch)
{
    rString3 += i + ";";
}

结果:
rString1是5;-7;0;3;
rString2是-7;0;3;5;
rString3是3;5;

说明:
创建一个List<int>的集合,rString1显示的是原来的顺序

接着,创建一个Comparison<Vector>类型的委托sorter
它匹配方法Compare(),该方法用于比较(排序)
Compare()方法必须符合格式:
int method(Vector objectA, Vector objectB)
这样就可以使用Sort()方法进行排序:route.Sort(sorter);
rString2显示了排序后集合元素的顺序

然后,创建一个Predicate<T>类型的委托searcher
它匹配方法Search(),该方法用于搜索
Search()方法必须符合格式:
bool method (T targetObject)
使用FindAll()方法返回一个包含所有匹配条件元素的集合赋給routeSearch
rString2显示了所有匹配条件的元素

3 Dictionary<K, V>
这个类型可以定义键/值对的集合
与其他泛型集合类型不同,这个类需要实例化两个类型,分别用于键和值,以表示集合中的

各个项。

例如:
Dictionary<string, int> things = new Dictionary<string, int>();
things.Add("Green Things", 29);
things.Add("Blue Things", 94);

可以使用Keys和Values属性迭代集合中的键和值:
foreach (string key in things.Keys)
{
    //Key
}

foreach (int value in things.Values)
{
    //Value
}

可以迭代集合中的各个项,把每个项作为一个KeyValuePair<K, V>实例来获取:
foreach (KeyValuePair<string, int> thing in things)
{
    //thing.Key, thing.Value
}

注意:每个项的键都必须是惟一的
如果要添加的项的键与已有项的键相同,就会抛出ArgumentException异常
也可以给构造函数传递初始容量(使用int)或项的集合(使用IDictionary<K,V>接口)

 

定义泛型


定义泛型类

要创建泛型类,只需在类定义中包含尖括号语法:
class MyGenericClass<T>
{
    ...
}
其中T可以是任意标识符,只要遵循通常的C#命名规则即可

泛型类可以在其定义中包含任意多个类型,它们用逗号分隔开
例如:
class MyGenericClass<T1, T2, T3>
{
    ...
}

例如:
class MyGenericClass<T1, T2, T3>
{
    private T1 innerT1Object;
    public MyGenericClass(T1 item)
    {
        innerT1Object = item;
    }
    public T1 InnerT1Object
    {
        get
        {
            return innerT1Object;
        }
    }
}

注意:不能假定类提供了什么类型
例如:
下面的代码就不会编译
class MyGenericClass<T1, T2, T3>
{
    private T1 innerT1Object;
    public MyGenericClass()
    {
        innerT1Object = new T1();
    }
}
对于未知的类型不能使用new来构造实例

如果不使用涉及高级技术的复杂代码,则可以安全地对T1进行如下假设:
可以把它看作继承自System.Object的类型或封箱到System.Object中。

即可以使用一些继承自System.Object的方法和属性
可以对集合进行操作,因为不需要对对象类型进行任何假设
注意在比较为泛型类型提供的类型值和null时,只能使用运算符==和!=
要对泛型进行实际的操作,需要更多地了解类中使用的类型

1 default关键字
如上例,当不知道用于创建泛型类实例的类型时,不能:
public MyGenericClass()
{
    innerT1Object = null;
}
因为如果T1是值类型,则innerT1Object不能是null

使用default关键字可以解决这个问题
例如:
public MyGenericClass()
{
    innerT1Object = default(T1);
}
如果innerT1Object是引用类型,就给它赋予null
如果它是值类型,就给它赋予默认值
例如对于数字类型,这个默认值是0,其他有可能是null等
即赋予T1类型变量一个可以使用的默认值

2 约束类型
对于没有进行任何约束的泛型类的类型,称为无绑定(unbounded)类型
而通过约束类型,可以把类型限制为用于实例化泛型类
例如,可以把类型限制为继承自某个类型

在类定义中,这可以使用where关键字来实现
例如:
class MyGenericClass<T> where T : constraint
{
    ...
}
其中constraint定义了约束。

可以提供多个约束,各个约束间用逗号分隔开:
class MyGenericClass<T> where T : constraint1, constraint2
{
    ...
}

还可以使用多个where语句,定义泛型类需要的任意类型或所有类型上的约束:
class MyGenericClass<T1, T2> where T1 : constraint1 where T2 : constraint2
{
    ...
}

约束必须出现在继承说明符的后面:
class MyGenericClass<T1> : MyBaseClass where T1 : constraint1
{
    ...
}

一些可用的约束
约束  定义  示例
struct  类型必须是值类型
 在类中,需要值类型才能起作用,例如,类中T类型的成员变量是0,表示某种含义
class  类型必须是引用类型
 在类中,需要引用类型才能起作用,例如,类中T类型的成员变量是null,表示某种含义
base class 类型必须是基类或继承自基类
 在类中,需要继承自基类的某种基本功能,才能起作用
interface 类型必须是接口或实现了接口
 在类中,需要接口提供的某种基本功能,才能起作用
new()  类型必须有一个公共的无参构造函数
 在类中,需要能实例化T类型的变量,例如在构造函数中实例化

注意:如果new()用作约束,它就必须是为类型指定的最后一个约束。

可以把一个类型参数用作另一个类型参数的约束
例如:
class MyGenericClass<T1, T2> where T2 : T1
{
    ...
}

其中,T2必须与T1的类型相同,或者继承自T1,这叫裸类型约束
裸类型约束(naked type constraint)表示一个泛型类型参数用作另一个类型参数的约束。

类型约束不能循环
例如:
下面代码不能编译:
class MyGenericClass<T1, T2> where T2 : T1 where T1 : T2
{
    ...
}

3 从泛型类中继承
从泛型类中继承需要遵循一些规则

首先,如果某个类型在它所继承的基类型中受到了约束,该类型就不能“解除约束”
即类型T在所继承的基类型中使用时,该类型必须受到至少与基类型相同的约束
这个规则适用于本章前面介绍的所有约束类型

另外,如果继承了一个泛型类型,就必须提供所有必须的类型信息
这可以使用其他泛型类型参数的形式来提供,也可以显式提供
这也适用于继承了泛型类型的非泛型类

4 泛型运算符
在C#中,可以像其他方法一样进行运算符的重写,这也可以在泛型类中实现。

5. 泛型结构
结构是值类型,泛型结构可以用与泛型类相同的方式来创建
例如:
public struct MyStruct<T1, T2>
{
    public T1 item1;
    public T2 item2;
}


定义泛型接口

定义泛型接口与定义泛型类所使用的技术相同
例如:
interface MyFarmingInterface<T> where T : Animal
{
    ....
}

其继承规则与类相同


定义泛型方法

在泛型方法中,返回类型和参数类型由泛型类型参数来确定
例如:
public T GetDefault<T>()
{
    return default(T);
}
使用default关键字,为类型T返回默认值

使用:
int myDefaultInt = GetDefault<int>();
在调用该方法时,类型T作为参数提供给函数

注意,这个T与用于给类提供泛型类型参数的类型完全不同
泛型方法可以通过非泛型类来实现
例如:
public class Defaulter
{
    public T GetDefault<T>()
    {
        return default(T);
    }
}

但如果类是泛型的,就必须为泛型方法类型使用不同的标识符
例如:
下面的代码不会编译:
public class Defaulter<T>
{
    public T GetDefault<T>()
    {
        return default(T);
    }
}
必须重命名方法或类使用的T。

泛型方法参数可以以与类相同的方式使用约束,在此可以使用任意类类型参数
例如:
public class Defaulter<T1>
{
    public T2 GetDefault<T2>() where T2 : T1
    {
        return default(T2);
    }
}

其中,为方法提供的类型T2必须与给类提供的T1相同,或者继承自T1
这是约束泛型方法的常用方式


定义泛型委托

前面分别为排序和搜索使用了Comparison<T>和Predicate<T>委托
例如:
一般定义委托:
public delegate int MyDelegate(int op1, int op2);
要定义泛型委托,只需声明和使用一个或多个泛型类型参数
public delegate T1 MyDelegate<T1, T2>(T2 op1, T2 op2) where T1 : T2;

可以使用约束,其规则也与前面一样 

原创粉丝点击