Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 592
gregsdennis Avatar asked Feb 21 '13 15:02

gregsdennis


1 Answers

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.

like image 68
Timothy Shields Avatar answered Nov 08 '22 00:11

Timothy Shields