简体   繁体   中英

Determine type equivalence

Is it possible in C# (I'm using .Net 4.5, but asking more generally) to determine if two implementations of a generic type are functionally equivalent?

As an example of the need, suppose I have an IMapper interface defined as:

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

My ideal implementation of this is:

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

This would be functionally equivalent to:

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

but the compiler (rightly) recognizes these as different types. Is there a way I can tell the compiler to treat these two types as equivalent (and perhaps even interchangeable)?

I've also looked at this:

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) {...}
}

but I found that I can't properly identify an abstraction so that I can inject (into constructors, etc.) the concrete class without creating a "middle-man" interface:

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) {...}
}

I think the "middle-man" approach is more "correct", but I'd prefer not to create a superfluous type. Also, it still carries the problem where swapping the type arguments creates two different, yet functionally equivalent , types.

Is there a better way to achieve my goal?

Given this definition:

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

The types IMapper<A, B> and IMapper<B, A> are actually not equivalent because of the asymmetric covariant/contravariant generic parameters. But, ignoring that...

You could try something like the following (although this might have problems when A and B have the same type).

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

Now, for example, you can define an IUndirectedMapper<int, string> some place in your code and then use it as both a IBidirectionalMapper<int, string> and a IBidirectionalMapper<string, int> .

Edit

These definitions give you three errors of the following flavor.

IBidirectionalMapper<A,B> cannot implement both IDirectionalMapper<A,B> and IDirectionalMapper<B,A> because they may unify for some type parameter substitutions

Looks like this approach doesn't work, sorry.

You could have a single mapper that does both translations but just have it be oriented, since in your case there is always a "my object" and "database object" pairing.

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

There is no way to express the concept that a IGenericInterface<T,U> should be considered equivalent to an IGenericInterface<U,T> , since the methods of those interfaces will be bound differently. For example, if a class implementing such an interface

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

The choice of which function is called depends upon the roles of T and U. If one does something like:

MyClass2<T,U>
{

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

The Convert method above will bind to the method that outputs "U to T", even within something like a MyClass2<Foo,Foo> where both types are the same.

It's really unclear what you mean by "equivalence". If you mean two types are equivalent because they have the same members and the members have identical signatures then you could use reflection to determine that:

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

If order of the members doesn't matter:

    public static bool AreTypesEqual2(Type type1, Type type2)
    {
        return type1.GetMembers().OrderBy(e=>e.ToString()).
            SequenceEqual(type2.GetMembers().OrderBy(e=>e.ToString()), new TypeComparer());
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM