分享

技术图文:C#语言中的泛型 II

 老马的程序人生 2020-09-11

C#语言中的泛型 II

知识结构:

图1 知识结构

6. 泛型接口

泛型类与泛型接口结合使用是很好的编程习惯,比如用IComparable<T>而非IComparable,以避免值类型上的装箱和拆箱操作。若将接口指定为类型参数的约束(接口约束),则要使用实现此接口的类型。在设计泛型接口时,我们需要注意以下几点:

(1)一个接口可以定义多个类型参数,如下所示:

public interface IDictionary<TK, TV>
{
//...
}

(2)可以用多个接口为类型参数指定接口约束,如下所示:

public class Stack<T> where T : IComparable<T>, IEnumerable<T>
{
//...
}

(3)具体类可以实现封闭结构类型的接口,如下所示:

public interface IBaseInterface<T>
{
//...
}

public class SampleClass : IBaseInterface<string>
{
//...
}

(4)泛型类可以实现泛型接口但要注意类型参数,如下所示:

public interface IBaseInterface1<T>
{
//...
}

public interface IBaseInterface2<T, TU>
{
//...
}

public class SampleClass1<T> : IBaseInterface1<T>
{
// 泛型类实现开放构造类型的接口
}

public class SampleClass2<T> : IBaseInterface2<T, string>
{
// 除了与接口共用的类型参数外,必须为所有的其它类型参数指定类型
}

(5)适用于类的继承规则同样适用于接口,如下所示:

public interface IMonth<T>
{
//...
}

public interface IMonth<T,U>
{
//...
}

public interface IJanuary : IMonth<int>
{
//...
}

public interface IFebruary<T> : IMonth<T>
{
//...
}

public interface IMarch<T> : IMonth<T, int>
{
//...
}

//public interface IApril<T> : IMonth<T, U>
//{
// // Error
//}

7. 泛型方法

(1)泛型方法的定义

声明了类型参数的方法,称为泛型方法。如下所示:

class Program
{

private static void Swap<T>(ref T lhs, ref T rhs)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}

public static void TestSwap()
{
int a = 1, b = 2;
Swap<int>(ref a, ref b); //int作为类型参数,调用方法。
//也可以忽略类型参数<int>,与上面的语句等价,编译器会自动推断它。
Swap(ref a, ref b);
Console.WriteLine(a + " " + b);
}

static void Main(string[] args)
{
TestSwap();// 1 2
}
}

静态方法和实例方法有着同样的类型推断规则。

类型推断对没有参数的方法是无效的,因为编译器无法单独根据约束或返回值来进行推断。

类型推断发生在编译器解析重载方法标志之前,对所有同名的泛型方法应用类型推断逻辑。在决定重载的阶段,编译器只包含哪些类型推断成功的泛型类。

(2)泛型方法的参数类型约束

private void SwapIfGreater<T>(ref T lhs, ref T rhs)
where T : IComparable<T>
{
//只能与实现IComparable<T>的类型参数一起使用。
if (lhs.CompareTo(rhs) > 0)
{
T temp = lhs;
lhs = rhs;
rhs = temp;
}
}

(3)泛型方法的重载

泛型方法可以通过多个类型参数来重载。

例如,下面的这些方法可以放在同一个类中:

private void DoWork()
{
//...
}
private void DoWork<T>()
{
//...
}
private void DoWork<T, TU>()
{
T a = default(T);
TU b = default(TU);
//...
}

(4)泛型类中的泛型方法

public class SampleClass<T>
{

private void Swap(ref T lhs, ref T rhs)
{
// 非泛型方法能访问所在类中的类型参数
}
}

public class GenericList<T>
{

private void SampleMethod<T>()
{
// 警告 CS0693
// 类型参数“T”与外部类型“GenericList<T>”中的类型参数同名
}
}
public class GenericList2<T>
{

private void SampleMethod<TU>()
{
//No Warning
}
}

若定义的泛型方法和其所在的类具有相同的类型参数,编译器就会产生警告CS0693,因为在方法范围内为内部T提供的参数隐藏了为外部T提供的参数。如果需要使用其它类型参数(而不是实例化类时提供的类型参数)来灵活地调用泛型类方法,要为方法的类型参数提供另一个标识符。

泛型方法实例:

public interface IAccount
{
string Name { get; }
decimal Balance { get; }
}

public class Account : IAccount
{
public string Name { get; }
public decimal Balance { get; }
public Account(string name, decimal balance)
{
Balance = balance;
Name = name;
}
}

public class Algorithm
{

public static decimal Total<T>(IEnumerable<T> e)
where T : IAccount
{
decimal total = 0;
foreach (T a in e)
{
total += a.Balance;
}
return total;
}
}

class Program
{

static void Main(string[] args)
{
List<Account> accounts = new List<Account>();
accounts.Add(new Account("Tom", 999.99m));
accounts.Add(new Account("Jonh", 1241.33m));
accounts.Add(new Account("Patrick", 1551.2m));
accounts.Add(new Account("Candy", 2647m));

decimal total = Algorithm.Total(accounts);
Console.WriteLine("total:{0:C}", total);
// total:¥6,439.52
}
}

8. 泛型委托

(1)泛型委托的定义

public delegate void Del<T>(T item);

class Program
{

public static void Notify(int i)
{
//...
}
static void Main(string[] args)
{
Del<int> m1 = new Del<int>(Notify);
// ...
}
}

使用时与实例化泛型类或调用泛型方法一样,需要指定类型参数。

C# 2.0之后有个新特性为 方法组转换(Method Group Conversion,从方法组到一个兼容委托类型的隐式转换,“方法组”可以看作一个“方法名”),普通委托和泛型委托都可以使用这一特性进行语法简化,如下所示:

public delegate void Del<T>(T item);

class Program
{

public static void Notify(int i)
{
//...
}
static void Main(string[] args)
{
Del<int> m1 = Notify;
// ...
}
}

(2)在泛型类中定义的委托,可以与类的方法一样使用泛型类的类型参数。

public class Stack<T>
{

private T[] _items;
private int _index;
public delegate void StackDelegate(T[] items);
}

class Program
{

private static void DoWork(float[] items)
{
//...
}

static void Main(string[] args)
{
Stack<float> s = new Stack<float>();
Stack<float>.StackDelegate d = DoWork;
// ...
}
}

(3)泛型委托在定义事件时sender可以定义为强类型,不用再与Object类型相互转换。

public delegate void StackEventHandler<T, TU>(T sender, TU eventArgs);

public class Stack<T>
{

public class StackEventArgs : EventArgs
{
public T X { get; set; }
public T Y { get; set; }
}

public event StackEventHandler<Stack<T>, StackEventArgs> StackEvent;
public virtual void OnStack(StackEventArgs a)
{
StackEvent?.Invoke(this, a);
}
}

public class SampleClass
{

public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args)
{
//...
Console.WriteLine("Test Delegate<T>:{0},{1}", args.X, args.Y);
}
}

class Program
{

static void Main(string[] args)
{
Stack<string> s = new Stack<string>();
s.StackEvent += new StackEventHandler<Stack<string>, Stack<string>.StackEventArgs>
(new SampleClass().HandleStackChange);
//s.StackEvent += new SampleClass().HandleStackChange;//与上一行语句等价
Stack<string>.StackEventArgs args1 = new Stack<string>.StackEventArgs();
args1.X = "Hello";
args1.Y = "World";
s.OnStack(args1);
//Test Delegate<T>:Hello,World
}
}

9. default关键字

在泛型类和泛型方法中会出现一个问题:如果把缺省值赋给参数化类型,此时无法预先知道以下两点:

  • T将是值类型还是引用类型。
  • 如果T是值类型,那么T将是数值还是结构。

对于一个参数化类型T的变量t,仅当T是引用类型时,t = null语句才是合法的;t = 0只对数值的有效,而对结构体则不行。

这个问题的解决办法是使用default关键词,它对引用类型返回null,对值类型的数值型返回零。而对于结构,它将返回结构每个成员,并根据成员是值类型还是引用类型,返回零或null

public class GenericList<T>
{

private class Node
{

//...
public Node Next;
public T Data;
}
//...
private Node head;
private Node current;

public T GetNext()
{
T temp = default(T);
if (current != null)
{
temp = current.Data;
current = current.Next;
}
return temp;
}
}

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多