用于类型映射的正确数据类型是什么?

3

我想实现一个基础的映射系统,类似于AutoMapper,但所有映射都是显式的,没有基于约定的映射。

为了做到这一点,我编写了一个类,它应该维护一个"映射"函数注册表,并在需要时查找它们,以将一个类型映射到另一个类型。我设想的用法是这样的:

在启动过程中的某个地方:

Mapper.Register<TypeA, TypeB>(typeA =>
{
    var typeB = new TypeB()
    {
        Property1 = typeA.Property1
    };

    return typeB;
}

然后当我想执行映射时...

TypeA typeA = new TypeA();
TypeB typeB = Mapper.Map<TypeA, TypeB>(typeA);

目前我使用了一个Dictionary<Tuple<Type, Type>, Delegate>来存储这个映射寄存器,但它并不像我所希望的那样工作...

public static class Mapper
{
    private readonly static Dictionary<Tuple<Type, Type>, Delegate> Mappings = new Dictionary<Tuple<Type, Type>, Delegate>();

    public static void Register<TSource, TDestination>(Func<TSource, TDestination> mappingFunction)
        where TSource : class
        where TDestination : class
    {
        Delegate mappingFunc;

        if (Mappings.TryGetValue(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), out mappingFunc))
            Mappings[new Tuple<Type, Type>(typeof (TSource), typeof (TDestination))] = mappingFunction;
        else
            Mappings.Add(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), mappingFunction);
    }

    public static TDestination Map<TSource, TDestination>(TSource src)
        where TSource : class
        where TDestination : class
    {
        Delegate mappingFunc;

        if (!Mappings.TryGetValue(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), out mappingFunc))
            throw new Exception("Invalid mapping: no mapping found for requested types.");

        var func = mappingFunc as Func<TSource, TDestination>;

        if (func == null)
            throw new Exception("Invalid mapping: no mapping found for requested types.");

        return func.Invoke(src);
    }
}

使用此代码时,注册映射正常工作,但从字典中检索映射失败,我认为这是由于 Map 方法中的第三行导致的:

Mappings.TryGetValue(new Tuple<Type, Type>(typeof (TSource), typeof (TDestination)), out mappingFunc)

这行代码总是无法通过测试,如果我理解正确,这是因为Tuple是引用类型,所以一个新的Tuple<Type, Type>实例,无论Item1Item2是什么,都将永远不会匹配Dictionary中的任何键。因此,Dictionary<Tuple<Type, Type>, Delegate>不适合存储此映射注册表。在这种情况下,什么数据类型是适合的?

它是如何“未能通过测试”的? - Thorkil Holm-Jacobsen
抱歉,我的意思是“if”语句总是返回false。 - Nick Coad
1个回答

3
当我尝试运行您上面的代码时,我得到了我期望的结果:
Mapper.Register<string, Regex>(s => new Regex("not using the given string"));
Mapper.Register<string, Regex>(s => new Regex(s));
var regex = Mapper.Map<string, Regex>(@"\w*");
// regex is now the Regex object instantiated with @"\w*"

换句话说,您的代码似乎运行正确。
这一行始终无法通过测试,我认为如果我理解正确,那是因为元组是引用类型,因此无论 Item1Item2 是什么,Tuple<Type, Type> 的新实例都永远不会匹配 Dictionary 中的任何键。
实际上,Tuple 的实现支持您正在尝试的操作。 Dictionary 在幕后使用 GetHashCodeEquals 方法进行查找。 Equals 方法通常检查对象的引用相等性,但是 源代码 显示,Tuple 被专门编程为使用结构相等性:
    public override Boolean Equals(Object obj) {
        return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);;
    }

    Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
        if (other == null) return false;

        Tuple<T1, T2> objTuple = other as Tuple<T1, T2>;

        if (objTuple == null) {
            return false;
        }

        return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2);
    }

Dictionary是处理这种情况的正确方式,因为可以进行任意数量的注册并且我们希望确保快速查找。然而,我认为我可能仍然会创建自己的类型TypeMapping来用于Dictionary,而不是使用Tuple<Type,Type>,因为我认为元组不能表达意图/用法。只需记住重写GetHashCodeEquals,以使其与Dictionary正确地运行:

public class TypeMapping : IStructuralEquatable
{
    public Type From { get; private set; }
    public Type To { get; private set; }

    public TypeMapping (Type from, Type to)
    {
        From = from;
        To = to;
    }

    public override int GetHashCode()
    {
        return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default);
    }

    int IStructuralEquatable.GetHashCode(IEqualityComparer comparer)
    {
        var hash = 17;
        unchecked
        {
            hash = hash * 31 + From.GetHashCode();
            hash = hash * 31 + To.GetHashCode();
        }
        return hash;
    }

    public override bool Equals(Object obj) {
        return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);
    }

    bool IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) {
        if (other == null) return false;

        var otherMapping = other as TypeMapping;
        if (otherMapping == null) return false;

        return comparer.Equals(From, otherMapping.From) && comparer.Equals(To, otherMapping.To);
    }
}

然后你的 Mapping 类中的 Dictionary 将会像这样(同时更改 Registration 和 Map 方法):

private readonly static Dictionary<TypeMapping, Delegate> Mappings = new Dictionary<TypeMapping, Delegate>();

非常感谢,这是我最终采取的方法,而且效果很好。 - Nick Coad
@NickCoad 如果你有兴趣,我刚刚添加了“TypeMapping”的示例代码。 - Thorkil Holm-Jacobsen

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