确定类型等价性

7

在C#中(我使用的是.NET 4.5,但更普遍的询问),是否有可能确定两个泛型类型的实现在功能上是等价的?

需要举一个例子来说明,假设我有一个定义为IMapper的接口:

public interface IMapper<TTo, TFrom>
{
    TTo MapFrom(TFrom obj);
    TFrom MapFrom(TTo obj);
}

我的理想实现方式是:

public class MyMapper : IMapper <A, B>
{
    A MapFrom(B obj) {...}
    B MapFrom(A obj) {...}
}

这将在功能上等同于:
public class MyEquivalentMapper : IMapper <B, A>
{
    B MapFrom(A obj) {...}
    A MapFrom(B obj) {...}
}

但编译器(正确地)将它们识别为不同的类型。有没有办法告诉编译器将这两种类型视为等效(甚至可互换)?

我也看了这个:

public interface ISingleMapper<out TTo, in TFrom>
{
    TTo MapFrom(TFrom obj);
}
public class MyAlternateMapper :
    ISingleMapper<A, B>,
    ISingleMapper<B, A>
{
    A MapFrom(B obj) {...}
    B MapFrom(A obj) {...}
}

但是我发现我无法正确识别抽象类,以便在构造函数等中注入具体的类而不创建“中间人”接口:

public interface IBidirectionalMapper<TTo, TFrom> :
    ISingleMapper<TTo, TFrom>,
    ISingleMapper<TFrom, TTo>
{
    TTo MapFrom(TFrom obj);
    TFrom MapFrom(TTo obj);
}
public class MyAlternateMapper : IBidirectionalMapper<A, B>
{
    A MapFrom(B obj) {...}
    B MapFrom(A obj) {...}
}

我认为“中间人”方法更加“正确”,但我不想创建一个多余的类型。同时,它仍然存在一个问题,即交换类型参数会创建两种不同但功能等效的类型。

有更好的方法实现我的目标吗?

4个回答

1
给定以下定义:
public interface IMapper<out TTo, in TFrom>
{
    TTo MapFrom(TFrom obj);
    TFrom MapFrom(TTo obj);
}

类型IMapper<A, B>IMapper<B, A>实际上并不等价,因为它们的泛型参数是不对称的。但是,如果忽略这一点...

你可以尝试以下代码(虽然当A和B具有相同类型时可能会出现问题)。

//Represents an oriented one-way mapper
public interface IDirectionalMapper<A, B>
{
    B Map(A obj);
}

//Represents an oriented two-way mapper
public interface IBidirectionalMapper<A, B>
    : IDirectionalMapper<A, B>, IDirectionalMapper<B, A>
{
}

//Represents an unoriented two-way mapper
public interface IUndirectedMapper<A, B>
    : IBidirectionalMapper<A, B>, IBidirectionalMapper<B, A>
{
}

现在,例如,您可以在代码中的某个地方定义一个IUndirectedMapper<int, string>,然后将其用作IBidirectionalMapper<int, string>IBidirectionalMapper<string, int>
编辑
这些定义会给您三个以下类型的错误。 IBidirectionalMapper<A,B>无法同时实现IDirectionalMapper<A,B>IDirectionalMapper<B,A>,因为它们可能对一些类型参数替换进行统一。
看起来这种方法行不通,抱歉。

1
(目前不在 LinqPad 前面)你的 IUndirectedMapper 的定义不会导致可怕的“类型统一”编译时错误吗? - JerKimball
哎呀...你说得对。而且没有支持where A != B的功能。 - Timothy Shields
@TimothyShields,感谢您指出协变/逆变问题。我已经进行了更正。看起来我只需要采用一种编码惯例,比如始终按字母顺序声明我的映射器类型之类的东西。 - gregsdennis
@TimothyShields,目标就是我发布的示例。我正在尝试将数据库表条目映射到我的.Net对象实体中,然后再映射回来。拥有一个执行两种转换的单个映射器比拥有两个映射器更好。我只是想看看是否有更好的方法。 - gregsdennis
你可以使用一个单一的映射器来完成两种翻译,但是只需将其定向,因为在你的情况下总是存在“我的对象”和“数据库对象”的配对。interface DbTypeModel<T, TDb> { T FromDb(TDb); TDb ToDb(T); } - Timothy Shields
显示剩余2条评论

1

您可以使用一个单独的映射器来进行两种翻译,但是请将其定向,因为在您的情况下始终存在“我的对象”和“数据库对象”的配对。

interface IDbTypeModel<T, TDb>
{
    T FromDb(TDb dbObj);
    TDb ToDb(T obj);
}

0

无法表达一个概念,即IGenericInterface<T,U>应被视为等同于IGenericInterface<U,T>,因为这些接口的方法将被不同地绑定。例如,如果实现此类接口的类

MyClass<T,U> : IGenericInterface<T,U>
{
  public T Convert(U param) { Console.WriteLine("U to T"); }
  public U Convert(T param) { Console.WriteLine("T to U"); }
}

调用哪个函数取决于 T 和 U 的角色。如果执行以下操作:
MyClass2<T,U>
{

  ... inside some method
    T myT;
    U myU;
    IGenericInterface<T,U> myThing = new MyClass<T,U>();
     myT = myThing.Convert(myU);
}

上面的Convert方法将绑定到输出"U to T"的方法,即使在像MyClass2<Foo,Foo>这样两种类型相同的情况下也是如此。

0

“等价”这个词非常含糊不清,不太确定您具体指的是什么。如果您的意思是两种类型因为有相同的成员变量且成员变量拥有相同的特征而被判定为等价,那么可以使用反射来判断:

    public class TypeComparer : IEqualityComparer<MemberInfo>
    {
        public bool Equals(MemberInfo x, MemberInfo y)
        {
            return x.ToString() == y.ToString();
        }

        public int GetHashCode(MemberInfo obj)
        {
            return obj.GetHashCode();
        }
    }

    public static bool AreTypesEqual(Type type1, Type type2)
    {
        return type1.GetMembers().
            SequenceEqual(type2.GetMembers(), new TypeComparer());
    }

如果成员的顺序不重要:
    public static bool AreTypesEqual2(Type type1, Type type2)
    {
        return type1.GetMembers().OrderBy(e=>e.ToString()).
            SequenceEqual(type2.GetMembers().OrderBy(e=>e.ToString()), new TypeComparer());
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接