通用类型映射






4.42/5 (9投票s)
一个将一个类型的值分配给另一个类型的实用程序。
背景
我最近遇到了一个需求,需要能够将一个类型的值分配给另一个我无法控制的类型的值。一个供应商提供了一个程序集,该程序集公开了一个代理,该代理接受的消息类型与我们内部使用的类型结构差异很大。通过 Reflector 对程序集进行快速查看,发现它编写得非常糟糕(而且我指的是非常糟糕)。我编写了这个小实用程序,允许我们的软件将我们在内部使用的消息类型映射到第三方程序集公开的消息类型。虽然我针对我的特定需求实现了这个功能,但我想我也可以与世界其他地方分享它,以防万一某人,在某个地方,可能会面临类似的问题并发现它有用。
设计目标
我想要一个简单的实用程序,通过单个调用就可以分配(“映射”)一个类型的值给另一个类型。我希望它既灵活又可扩展。在我想出一个我认为简单的解决方案之前,我考虑过使用委托、序列化以及各种其他使用泛型的技巧。我不想为了完成映射而费太多功夫,并且希望在需要映射器的地方进行尽可能简单的调用。我决定将目标列为
- 灵活
- 可扩展
- 简单
- DRY(即,不要重复自己)
- 支持 IoC(虽然不是先决条件)
最后,我在我的客户端代码中得到了类似这样的结果
Mapper mapper = new Mapper(provider);
TypeB typeB = mapper.Map<TypeB>(typeA);
工作原理
在幕后,Mapper
使用少数几个接口和泛型。
IMappable
非常简单的空接口。允许一个类作为映射的输入。这将是您 **确实** 可以控制的类型。它是一个空接口,故意这样做的目的是为了在使用它时没有任何需要满足的条件。
public interface IMappable {}
IMappingProvider
为将提供映射实现的组件提供服务契约。该方法期望为其用法提供一个类型参数。这是整个实用程序的核心。重要的是,泛型类型是方法的一部分,**而不是** 接口声明的一部分。
public interface IMappingProvider
{
T Map<T>(IMappable input);
}
ConcreteMappingProvider
实现在 IMappable
和一个或多个类型之间的具体映射。注意:这里给出的此 `ConcreteMappingProvider` 只是作为非常基本类型的演示。您会注意到它使用泛型 `<T>` 类型作为结果类型来实现 IMappingProvider
接口。接口实现(公共方法)将工作传递给一个私有方法,该方法将返回具体类型(即,您无法控制的类型)。它要求私有方法将结果作为 object
返回;这是因为直接从具体类型转换为 `<T>` 是非法的。但是,从 object
转换为 `<T>` **是**合法的。因此,您应该获得预期的类型,而没有装箱开销。
public class ConcreteMappingProvider : IMappingProvider
{
public T Map<T>(IMappable input)
{
object result = map(input as TypeA);
return (T)result;
}
private TypeB map(TypeA input)
{
TypeB result = new TypeB();
result.StringX = input.StringA;
result.intY = input.IntB;
result.StringZ = input.GuidC.ToString();
return result;
}
}
Mapper (映射器)
简单的外观,用于调用 IMappingProvider
的特定实现。这是您根据需要调用的外观,以返回您要寻找的特定映射类型。
public class Mapper
{
public IMappingProvider MappingProvider { get; private set; }
public Mapper(IMappingProvider provider)
{
this.MappingProvider = provider;
}
public T Map<T>(IMappable input)
{
return MappingProvider.Map<T>(input);
}
}
示例类型
这些是本文中使用的示例类型。请注意,TypeB
**没有** 继承自 IMappable
。这将是您无法控制的类型。
public class TypeA : IMappable
{
public string StringA { get; set; }
public int IntB { get; set; }
public Guid GuidC { get; set; }
}
public class TypeB
{
public string StringX { get; set; }
public int intY { get; set; }
public string StringZ { get; set; }
}
依赖注入
虽然 DI 对于使用此实用程序不是必需的,但通过将特定实现与外观分开,我为此做好了准备。使用您选择的 DI 容器/框架,您可以根据需要注册/解析特定的提供程序。我使用 Windsor 容器,因此下载示例看起来与以下内容类似
static void Main(string[] args)
{
// set up a new container
var container = new WindsorContainer();
// register the service/components
container.Register(
Component
.For<IMappingProvider>()
.ImplementedBy<ConcreteMappingProvider>()
);
// instance of a mappable type
TypeA typeA = new TypeA()
{
StringA = "some string",
IntB = 123,
GuidC = Guid.NewGuid()
};
// get the mapping provider and instantiate Mapper
IMappingProvider provider = container.Resolve<IMappingProvider>();
Mapper mapper = new Mapper(provider);
// expect TypeB returned
TypeB typeB = mapper.Map<TypeB>(typeA);
Console.ReadLine();
}
扩展解决方案
要扩展解决方案以满足您的需求,您最少需要做的是编写您自己的组件,该组件实现 IMappingProvider
。虽然这个提供程序可能会运行几百行代码(取决于您需要映射的内容),但这几乎就是您需要做的所有事情。
结论
如上所述,这是一个轻量级的类型映射器,它灵活、简单且可扩展。我希望它在您自己的代码中找到一个快乐的家。
历史
首次提交。