4

我已经为我的存储库创建了这个接口。

public interface IRepository<T, in TKey> where T: class
{
    IEnumerable<T> Find(Expression<Func<T, bool>> predicate);
    IEnumerable<T> FindAll();
    T FindSingle(TKey id);
    void Create(T entity);
    void Delete(T entity);
    void Update(T entity);
}

FindSingle方法接受一个 ID,该 ID 将用于搜索主键。通过使用in,我希望我只被允许将引用类型作为TKey. 出于好奇,我决定创建一个具体类并将其指定为 int,这样我就可以看到异常。

我查了MSDN,它指定这不应该工作

引用类型支持泛型类型参数中的协变和逆变,但值类型不支持它们。

我创建的类看起来像这样

public class ProjectRepository : IRepository<Project,int>
{
    public IEnumerable<Project> Find(Expression<Func<Project, bool>> predicate)
    {
        throw new NotImplementedException();
    }

    public IEnumerable<Project> FindAll()
    {
        throw new NotImplementedException();
    }

    public Project FindSingle(int id)
    {
        throw new NotImplementedException();
    }

    public void Create(Project entity)
    {
        throw new NotImplementedException();
    }

    public void Delete(Project entity)
    {
        throw new NotImplementedException();
    }

    public void Update(Project entity)
    {
        throw new NotImplementedException();
    }
}

为什么我在指定TKey为值类型的构建时没有得到异常?另外,如果我in从我的参数中删除了我失去了什么?MSDN 文档说逆变允许使用较少派生的类型,但通过删除in我可以肯定地传递任何类型,因为它仍然是通用的。

这可能表明对逆变和协方差缺乏了解,但这让我有点困惑。

4

4 回答 4

6

协变和逆变对值类型没有多大意义,因为它们都是密封的。尽管从文档中并不清楚,但使用 astruct作为协/逆变类型是有效的,但它并不总是有用的。您引用的文档很可能是指以下内容无效:

public struct MyStruct<in T>

逆变意味着您可以执行类似以下示例的操作:

IRepository<string, Base> b = //something
IRepository<string, Derived> d = b;

由于没有任何东西派生自int,因此您可以使用 an IRepository<string, int>,但只能用作IRepository<string, int>.

协变意味着你可以做相反的事情,例如IEnumerable<T>is out T,它是协变的。您可以执行以下操作:

IEnumerable<Derived> d = //something
IEnumerable<Base> b = d;

如果您尝试同时限制和TKeyes (引用类型),则应包括第二个限制:Tclass

public interface IRepository<T, in TKey>
    where T : class
    where TKey : class
于 2013-08-07T12:08:52.967 回答
2

实际上,您错过了协变和逆变的全部要点:-) 这是关于能够将泛型类型的变量分配给相同泛型类型但具有不同泛型类型参数的另一个变量,这些变量与源中使用的那些。
根据泛型类型参数是协变还是逆变,允许进行不同的赋值。

假设如下界面:

public interface IRepository<in T>
{
    void Save(T value);
}

此外,假设以下接口以及实现它的值类型和引用类型:

public interface IBar
{
}

public struct BarValueType : IBar
{
}

public class BarReferenceType : IBar
{
}

最后,假设两个变量:

IRepository<BarReferenceType> referenceTypeRepository;
IRepository<BarValueType> valueTypeRepository;

逆变现在意味着您可以将 的实例分配给IRepository<IBar>变量referenceTypeRepository,因为BarReferenceType实现IBar.
您引用的 MSDN 中的部分仅表示分配IRepository<IBar>to的实例valueTypeRepository是不合法的,尽管BarValueType也实现了IBar.

于 2013-08-07T12:13:03.517 回答
1

用值类型实现你的接口没有问题。IRepository<Project, object>例如,在尝试将 a 分配给 a时,您只会收到错误消息IRepository<Project, int>。在以下代码中,最后一个赋值不会编译:

public interface IContravariant<T, in TKey> where T : class
{
    T FindSingle(TKey id);
}
public class objCV : IContravariant<Project, object>
{
    public Project FindSingle(object id)
    {
        return null;
    }
    public static void test()
    {
        objCV objcv = new objCV();

        IContravariant<Project, Project> projcv;
        IContravariant<Project, int> intcv;

        projcv = objcv;
        intcv = objcv;
    }
}
于 2013-08-07T12:15:29.033 回答
0

在本文中,他们告诉我们类型参数被编译器视为不变:

差异仅适用于引用类型;如果为变体类型参数指定值类型,则该类型参数对于生成的构造类型是不变的。

来自:http: //msdn.microsoft.com/en-us/library/dd799517.aspx

于 2013-08-07T12:13:31.077 回答