C#——泛型接口中的变体

C#——泛型接口中的变体

NET Framework 4 引入了对多个现有泛型接口的变体支持。 变体支持允许实现这些接口的类进行隐式转换。
自 .NET Framework 4 起,以下接口为变体:

IEnumerable(T 是协变)

IEnumerator(T 是协变)

IQueryable(T 是协变)

IGrouping<TKey,TElement>(TKey 和 TElement 都是协变)

IComparer(T 是逆变)

IEqualityComparer(T 是逆变)

IComparable(T 是逆变)

自 .NET Framework 4.5 起,以下接口是变体:

IReadOnlyList(T 是协变)

IReadOnlyCollection(T 是协变)

协变允许方法具有的返回类型比接口的泛型类型参数定义的返回类型的派生程度更大。 若要演示协变功能,请考虑以下泛型接口:IEnumerable 和 IEnumerable。 IEnumerable 接口不继承 IEnumerable 接口。 但是,String 类型会继承 Object 类型,在某些情况下,建议为这些接口互相指派彼此的对象。 下面的代码示例对此进行了演示。

IEnumerable<String> strings = new List<String>();
IEnumerable<Object> objects = strings;

在旧版 .NET Framework 中,此代码会导致 C# 中出现编译错误;如果启用 Option Strict 条件,则会导致在 Visual Basic 中出现编译错误。 但现在可使用 strings 代替 objects,如上例所示,因为 IEnumerable 接口是协变接口。

逆变允许方法具有的实参类型比接口的泛型形参定义的类型的派生程度更小。 若要演示逆变,假设已创建了 BaseComparer 类来比较 BaseClass 类的实例。 BaseComparer 类实现 IEqualityComparer 接口。 因为 IEqualityComparer 接口现在是逆变接口,因此可使用 BaseComparer 比较继承 BaseClass 类的类的实例。 下面的代码示例对此进行了演示。

// Simple hierarchy of classes.
class BaseClass { }
class DerivedClass : BaseClass { }

// Comparer class.
class BaseComparer : IEqualityComparer<BaseClass>
{
    public int GetHashCode(BaseClass baseInstance)
    {
        return baseInstance.GetHashCode();
    }
    public bool Equals(BaseClass x, BaseClass y)
    {
        return x == y;
    }
}
class Program
{
    static void Test()
    {
        IEqualityComparer<BaseClass> baseComparer = new BaseComparer();

        // Implicit conversion of IEqualityComparer<BaseClass> to
        // IEqualityComparer<DerivedClass>.
        IEqualityComparer<DerivedClass> childComparer = baseComparer;
    }
}

有关更多示例,请参阅在泛型集合的接口中使用变体 (C#)

只有引用类型才支持使用泛型接口中的变体。 值类型不支持变体。 例如,无法将 IEnumerable 隐式转换为 IEnumerable,因为整数由值类型表示。

IEnumerable<int> integers = new List<int>();
// The following statement generates a compiler error,
// because int is a value type.
// IEnumerable<Object> objects = integers;

还需记住,实现变体接口的类仍是固定类。 例如,虽然 List 实现协变接口 IEnumerable,但不能将 List 隐式转换为 List。 以下代码示例阐释了这一点。

// The following line generates a compiler error
// because classes are invariant.
// List<Object> list = new List<String>();

// You can use the interface object instead.
IEnumerable<Object> listObjects = new List<String>();

创建变体泛型接口

接口中的泛型类型参数可以声明为协变或逆变。 协变允许接口方法具有与泛型类型参数定义的返回类型相比,派生程度更大的返回类型。 逆变允许接口方法具有与泛型形参指定的实参类型相比,派生程度更小的实参类型。 具有协变或逆变泛型类型参数的泛型接口称为“变体”。

声明变体泛型接口

可通过对泛型类型参数使用 in 和 out 关键字来声明变体泛型接口。

C# 中的 ref、in 和 out 参数不能为变体。 值类型也不支持变体。

可以使用 out 关键字将泛型类型参数声明为协变。 协变类型必须满足以下条件:

类型仅用作接口方法的返回类型,不用作方法参数的类型。 下例演示了此要求,其中类型 R 为声明的协变。

interface ICovariant<out R>
{
    R GetSomething();
    // The following statement generates a compiler error.
    // void SetSomething(R sampleArg);

}

此规则有一个例外。 如果具有用作方法参数的逆变泛型委托,则可将类型用作该委托的泛型类型参数。 下例中的类型 R 演示了此情形

interface ICovariant<out R>
{
    void DoSomething(Action<R> callback);
}

类型不用作接口方法的泛型约束。 下面的代码阐释了这一点。

interface ICovariant<out R>
{
    // The following statement generates a compiler error
    // because you can use only contravariant or invariant types
    // in generic constraints.
    // void DoSomething<T>() where T : R;
}

可以使用 in 关键字将泛型类型参数声明为逆变。 逆变类型只能用作方法参数的类型,不能用作接口方法的返回类型。 逆变类型还可用于泛型约束。 以下代码演示如何声明逆变接口,以及如何将泛型约束用于其方法之一。

interface IContravariant<in A>
{
    void SetSomething(A sampleArg);
    void DoSomething<T>() where T : A;
    // The following statement generates a compiler error.
    // A GetSomething();
}

此外,还可以在同一接口中同时支持协变和逆变,但需应用于不同的类型参数,如以下代码示例所示。

interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSomethings(A sampleArg);
}

实现变体泛型接口

在类中实现变体泛型接口时,所用语法和用于固定接口的语法相同。 以下代码示例演示如何在泛型类中实现协变接口。

interface ICovariant<out R>
{
    R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
    public R GetSomething()
    {
        // Some code.
        return default(R);
    }
}

实现变体接口的类是固定类。 例如,考虑下面的代码。

// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;

// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error
// because classes are invariant.
// SampleImplementation<Object> obj = button;

扩展变体泛型接口

扩展变体泛型接口时,必须使用 in 和 out 关键字来显式指定派生接口是否支持变体。 编译器不会根据正在扩展的接口来推断变体。 例如,考虑以下接口。

interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

尽管 IInvariant 接口和 IExtCovariant 接口扩展的是同一个接口,但泛型类型参数 T 在前者中为固定参数,在后者中为协变参数。 此规则也适用于逆变泛型类型参数。

无论泛型类型参数 T 在接口中是协变还是逆变,都可以创建一个接口来扩展这两类接口,只要在扩展接口中,该 T 泛型类型参数为固定参数。 以下代码示例阐释了这一点。

interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

但是,如果泛型类型参数 T 在一个接口中声明为协变,则无法在扩展接口中将其声明为逆变,反之亦然。 以下代码示例阐释了这一点。

interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }

避免多义性

实现变体泛型接口时,变体有时可能会导致多义性。 应避免这样的多义性。

例如,如果在一个类中使用不同的泛型类型参数来显式实现同一变体泛型接口,便会产生多义性。 在这种情况下,编译器不会产生错误,但未指定将在运行时选择哪个接口实现。 这种多义性可能导致代码中出现小 bug。 请考虑以下代码示例。

// Simple class hierarchy.
class Animal { }
class Cat : Animal { }
class Dog : Animal { }

// This class introduces ambiguity
// because IEnumerable<out T> is covariant.
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
    IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
    {
        Console.WriteLine("Cat");
        // Some code.
        return null;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        // Some code.
        return null;
    }

    IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
    {
        Console.WriteLine("Dog");
        // Some code.
        return null;
    }
}
class Program
{
    public static void Test()
    {
        IEnumerable<Animal> pets = new Pets();
        pets.GetEnumerator();
    }
}

在此示例中,没有指定 pets.GetEnumerator 方法如何在 Cat 和 Dog 之间选择。 这可能导致代码中出现问题。

在泛型集合的接口中使用变体

协变接口允许其方法返回的派生类型多于接口中指定的派生类型。 逆变接口允许其方法接受派生类型少于接口中指定的类型的参数。

在.NET Framework 4 中,多个现有接口已变为协变和逆变接口。 包括 IEnumerable 和 IComparable。 这使你可将对基类型的泛型集合进行操作的那些方法重用于派生类型的集合。

转换泛型集合

下例阐释了 IEnumerable 接口中的协变支持的益处。 PrintFullName 方法接受 IEnumerable 类型的集合作为参数。 但可将该方法重用于 IEnumerable 类型的集合,因为 Employee 继承 Person。

// Simple hierarchy of classes.  
public class Person  
{  
    public string FirstName { get; set; }  
    public string LastName { get; set; }  
}  
  
public class Employee : Person { }  
  
class Program  
{  
    // The method has a parameter of the IEnumerable<Person> type.  
    public static void PrintFullName(IEnumerable<Person> persons)  
    {  
        foreach (Person person in persons)  
        {  
            Console.WriteLine("Name: {0} {1}",  
            person.FirstName, person.LastName);  
        }  
    }  
  
    public static void Test()  
    {  
        IEnumerable<Employee> employees = new List<Employee>();  
  
        // You can pass IEnumerable<Employee>,
        // although the method expects IEnumerable<Person>.  
  
        PrintFullName(employees);  
  
    }  
}

比较泛型集合

下例阐释了 IComparer 接口中的逆变支持的益处。 PersonComparer 类实现 IComparer 接口。 但可以重用此类来比较 Employee 类型的对象序列,因为 Employee 继承 Person。

// Simple hierarchy of classes.  
public class Person  
{  
    public string FirstName { get; set; }  
    public string LastName { get; set; }  
}  
  
public class Employee : Person { }  
  
// The custom comparer for the Person type  
// with standard implementations of Equals()  
// and GetHashCode() methods.  
class PersonComparer : IEqualityComparer<Person>  
{  
    public bool Equals(Person x, Person y)  
    {
        if (Object.ReferenceEquals(x, y)) return true;  
        if (Object.ReferenceEquals(x, null) ||  
            Object.ReferenceEquals(y, null))  
            return false;
        return x.FirstName == y.FirstName && x.LastName == y.LastName;  
    }  
    public int GetHashCode(Person person)  
    {  
        if (Object.ReferenceEquals(person, null)) return 0;  
        int hashFirstName = person.FirstName == null  
            ? 0 : person.FirstName.GetHashCode();  
        int hashLastName = person.LastName.GetHashCode();  
        return hashFirstName ^ hashLastName;  
    }  
}  
  
class Program  
{  
  
    public static void Test()  
    {  
        List<Employee> employees = new List<Employee> {  
               new Employee() {FirstName = "Michael", LastName = "Alexander"},  
               new Employee() {FirstName = "Jeff", LastName = "Price"}  
            };  
  
        // You can pass PersonComparer,
        // which implements IEqualityComparer<Person>,  
        // although the method expects IEqualityComparer<Employee>.  
  
        IEnumerable<Employee> noduplicates =  
            employees.Distinct<Employee>(new PersonComparer());  
  
        foreach (var employee in noduplicates)  
            Console.WriteLine(employee.FirstName + " " + employee.LastName);  
    }  
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值