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

C#语言中的泛型 II

知识结构:

技术图文:C#语言中的泛型 II_第1张图片


6. 泛型接口

泛型类与泛型接口结合使用是很好的编程习惯,比如用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
//}

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作为类型参数,调用方法。
        //也可以忽略类型参数,与上面的语句等价,编译器会自动推断它。
        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
    }
}

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:{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
    }
}

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;
    }
}

后台回复「搜搜搜」,随机获取电子资源!
欢迎关注,请扫描二维码:



你可能感兴趣的:(C#学习)