知识结构:
泛型类与泛型接口结合使用是很好的编程习惯,比如用IComparable
而非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 : IMonth
//{
// // Error
//}
(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作为类型参数,调用方法。
//也可以忽略类型参数,与上面的语句等价,编译器会自动推断它。
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的类型参数一起使用。
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”中的类型参数同名
}
}
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
}
}
(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:{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:Hello,World
}
}
在泛型类和泛型方法中会出现一个问题:如果把缺省值赋给参数化类型,此时无法预先知道以下两点:
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;
}
}
后台回复「搜搜搜」,随机获取电子资源!
欢迎关注,请扫描二维码: