【问题标题】:Determine type equivalence确定类型等价
【发布时间】:2013-02-07 00:56:38
【问题描述】:

是否可以在 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) {...}
}

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

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

【问题讨论】:

    标签: c# types interface


    【解决方案1】:

    真的不清楚你所说的“等价”是什么意思。如果您的意思是两种类型是等效的,因为它们具有相同的成员并且成员具有相同的签名,那么您可以使用反射来确定:

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

    【讨论】:

      【解决方案2】:

      没有办法表达IGenericInterface&lt;T,U&gt; 应该被视为等同于IGenericInterface&lt;U,T&gt; 的概念,因为这些接口的方法将被不同地绑定。例如,如果一个类实现了这样的接口

      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 到 T”的方法,即使在类似MyClass2&lt;Foo,Foo&gt; 这样两种类型相同的情况下也是如此。

      【讨论】:

        【解决方案3】:

        您可以有一个映射器来进行两种翻译,但只是让它定向,因为在您的情况下,总是有“我的对象”和“数据库对象”配对。

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

        【讨论】:

          【解决方案4】:

          鉴于此定义:

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

          IMapper&lt;A, B&gt;IMapper&lt;B, A&gt; 类型实际上是不等价的,因为它们具有不对称的协变/逆变泛型参数。但是,忽略这一点...

          您可以尝试以下方法(尽管当 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&lt;int, string&gt;,然后将其用作IBidirectionalMapper&lt;int, string&gt;IBidirectionalMapper&lt;string, int&gt;

          编辑

          这些定义为您提供以下三种错误。

          IBidirectionalMapper&lt;A,B&gt; 不能同时实现 IDirectionalMapper&lt;A,B&gt;IDirectionalMapper&lt;B,A&gt;,因为它们可能会统一用于某些类型参数替换

          看来这种方法行不通,抱歉。

          【讨论】:

          • (目前不在 linqpad 前面)您对 IUndirectedMapper 的定义不会导致可怕的“类型统一”编译时错误吗?
          • 呃哦......你是对的。而且没有where A != B 支持。
          • @TimothyShields,感谢您指出协方差问题。我已经纠正了。它开始看起来像我只需要采用始终按字母顺序或类似顺序声明我的映射器类型的编码实践。
          • 如果您透露了您在此问题中概述的目标的动机,我们可能会考虑另一种方法。
          • @TimothyShields,目标我发布的示例。我正在尝试将数据库表条目映射到我的 .Net 对象实体并返回。拥有一个执行两种翻译的映射器比拥有两个映射器更好。我只是想看看有没有更好的方法。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-07-02
          • 1970-01-01
          • 2019-05-16
          • 1970-01-01
          相关资源
          最近更新 更多