具体 Class 覆盖的通用扩展方法

Generic Extension Method with Concrete Class Override

我有一个第三方 DLL returns 对象,如 CustomersOrders 等。我将它们称为 Your Entities。他们确实有一个通用的 IYourEntity 接口,因此我可以将其用作源约束。

我想创建一个通用的转换扩展方法,以使用一些简化且更易于维护的代码将所有这些不同的第三方实体转换为 My Entities

.....但我无法弄清楚如何制作一个通用扩展方法,该方法将为每个 class.

的特定转换调用具体扩展方法

将我的代码的一些主要方面放在下面,但您可以获得完整的 fiddle 来玩 here

是的,我可能表明我对如何执行此操作一无所知,并且可能会尝试结合不同的概念。任何指点都非常感谢,因为我已经打了几天脑袋,需要一条生命线:)

public interface IYourEntity
{
    int Id
    {
        get;
        set;
    }
}

public interface IConvertToMyEntity<TYourEntity, TMyEntity>
    where TYourEntity : class, IYourEntity, new()
    where TMyEntity : class, IMyEntity, new()
{
    TMyEntity ToMyEntity(TYourEntity yourEntity);
}

public static class ExtensionMethods
{
    private static IMyEntity ToMyEntity(this IYourEntity yourEntity)
    {
        return new MyEntity1();
    }

    public static List<IMyEntity> ToMyEntityList(this List<IYourEntity> lstYourEntities)
    {
        return lstYourEntities.ConvertAll(q => q.ToMyEntity());
    }
}

public class YourEntity1 : IYourEntity, IConvertToMyEntity<YourEntity1, MyEntity1>
{
    public int Id
    {
        get;
        set;
    }

    public string YourEntityName
    {
        get;
        set;
    }

    public MyEntity1 ToMyEntity(YourEntity1 yourEntity)
    {
        return new MyEntity1()
        {Id = yourEntity.Id, MyEntityName = yourEntity.YourEntityName, CreatedOn = DateTime.UtcNow};
    }

    public List<MyEntity1> ToMyEntityList(List<YourEntity1> lstYourEntities)
    {
        return lstYourEntities.ConvertAll(q => ToMyEntity(q));
    }
}

由于实现 IYourEntity 的 classes 来自第三方而不在您的控制之下,您不能在这些之上实现自己的 IConvertToMyEntity<T1, T2> 接口。

处理它的一种方法是重载此类转换(扩展)方法。
不需要任何通用的 T 类型参数;通用 IYourEntity 接口就足够了。

假设您有 3 个 class 实现 IYourEntity 接口;
例如YourCustomerYourOrderYourProduct

这些需要转换为 IMyEntity 个实例,您可能有不同的具体实现;
例如一般 MyEntity 和特定 MyProduct.

您为转化设置了一个针对 IYourEntity 的扩展方法。
如果不存在此扩展方法的更具体的重载,将调用此扩展方法将 IYourEntity 转换为 IMyEntity

public static IMyEntity ToMyEntity(this IYourEntity target)
{
     return new MyEntity { Id = target.Id, EntityName = "Fallback name" };
}

对于需要自定义转换的实体,您可以针对这些特定源 class 类型设置此扩展方法的重载。
以下是 YourOrderYourProduct 的此类(但不是 YourCustomer)。

public static IMyEntity ToMyEntity(this YourOrder target)
{
    return new MyEntity { Id = target.Id, EntityName = target.OrderName.ToUpper() };
}

public static IMyEntity ToMyEntity(this YourProduct target)
{
    return new MyProduct { Id = target.Id * 100, EntityName = target.ProductName };
}

接下来,定义扩展方法以将 IYourEntity 个实例列表转换为 IMyEntity 个实例列表。在下面的代码中,将中间转换为 dynamic 可以调用适当的 ToMyEntity 重载。

请注意,ToMyEntity 方法不一定是扩展方法,但如果您需要转换单个实例而不是列表.

public static List<IMyEntity> ToMyEntities(this List<IYourEntity> target)
{
    var myEntities  = new List<IMyEntity>();

    foreach (var yourEntity in target)
    {
        var myEntity = Extensions.ToMyEntity((dynamic)yourEntity);
        myEntities.Add(myEntity);
    }

    return myEntities;
}

一个例子 - .net fiddle

var yourEntities = new List<IYourEntity>()
{
    new YourCustomer() { Id = 1 },
    new YourOrder() { Id = 2, OrderName = "Order-2"},
    new YourProduct() { Id = 3, ProductName = "Product-3"}
 };

var myEnties = yourEntities.ToMyEntities();

myEnties.ForEach(o => Console.WriteLine("{0} - {1}  ({2})", 
    o.Id, o.EntityName, o.GetType().Name
));

上面示例的输出如下所示。
请注意 YourCustomer 实例是如何由一般 IYourEntity 转换处理的,而 YourOrderYourProduct 实例得到了特定的处理。

1 - Fallback name  (MyEntity)
2 - ORDER-2  (MyEntity)
300 - Product-3  (MyProduct)

您可以将扩展方法更改为:

    private static IMyEntity ToMyEntity(this IYourEntity yourEntity)
    {
        if (yourEntity is IConvertToMyEntity<IYourEntity, IMyEntity> convertible)
            return convertible.ToMyEntity;
        return new MyEntity1();
    }

这在大多数情况下是行不通的,除非你也让你的界面协变和反变:

public interface IConvertToMyEntity<in TYourEntity, out TMyEntity>
    where TYourEntity : class, IYourEntity, new()
    where TMyEntity : class, IMyEntity, new()
{
    TMyEntity ToMyEntity(TYourEntity yourEntity);
}

我仍然不完全清楚如何让第三方 class 如此轻松地实现 IConvertToMyEntity。假设你这样做只是为了向我们展示你的实际目标,你应该非常小心你在 Main.

中试图完成的事情

如果你使用List<IYourEntity>,你只能使用接口中定义的方法和属性,除非你知道你在用特定的转换做什么。 List<IYourEntity>List<IMyEntity> 的需要极大地限制了 My classes 和 Your classes 之间自定义映射器的实现。这里有一个可能的解决方案:

正如我所说,我没有改变Your classes:

public interface IYourEntity
{
    int Id
    {
        get;
        set;
    }
}

public class YourEntity1 : IYourEntity
{
    public int Id
    {
        get;
        set;
    }

    public string YourEntityName
    {
        get;
        set;
    }
}

另外 My classes 非常简单,不包含任何映射逻辑。这是一个值得商榷的选择,但我通常更喜欢将转换逻辑与所涉及的 classes 分开。这有助于保持代码整洁,以防同一对 classes 有多个转换函数。顺便说一句,他们在这里:

public interface IMyEntity
{
    int Id
    {
        get;
        set;
    }

    DateTime CreatedOn
    {
        get;
        set;
    }
}

public class MyEntity1 : IMyEntity
{
    public int Id
    {
        get;
        set;
    }

    public string MyEntityName
    {
        get;
        set;
    }

    public DateTime CreatedOn
    {
        get;
        set;
    }
}

这就是我设计自定义转换器的方式

public interface IMyEntityConverter
{
    IMyEntity Convert(IYourEntity yourEntity);
}

public class MyEntity1Converter : IMyEntityConverter
{
    public IMyEntity Convert(IYourEntity yourEntity) 
    {
        var castedYourEntity = yourEntity as YourEntity1;
        return new MyEntity1() 
        {
            Id = castedYourEntity.Id, 
            MyEntityName = castedYourEntity.YourEntityName, 
            CreatedOn = DateTime.UtcNow
        };
    }
}

很明显缺乏通用性,但是如果您需要在通用 MyYour classes 的 List 上的扩展方法,则不能这样做.还尝试使用协变和逆变接口,但 C# 不允许您在此实现中使用它们。

现在是解决方案的核心:您需要使用自定义转换器将 Your class 绑定到 My class,所有这些都应该是尽可能透明。

public class EntityAdapter<YourType, MyType>
    where YourType : IYourEntity
    where MyType : IMyEntity
{
    protected YourType wrappedEntity;
    protected IMyEntityConverter converter;
        
    public EntityAdapter(YourType wrappedEntity, IMyEntityConverter converter) 
    {
        this.wrappedEntity = wrappedEntity;
        this.converter = converter;
    }
    
    public static implicit operator YourType(EntityAdapter<YourType, MyType> entityAdapter) => entityAdapter.wrappedEntity;
    public static explicit operator MyType(EntityAdapter<YourType, MyType> entityAdapter) => 
        (MyType) entityAdapter.converter.Convert(entityAdapter.wrappedEntity);
        
    public MyType CastToMyEntityType()
    {
        return (MyType) this;
    }
}

此处的伪透明由 Your class 的隐式转换给出。优点是您可以通过调用 CastToMyEntityType 或显式运算符重载将此 EntityAdapter 转换为 My class 的实例。

痛苦的部分是扩展方法:

public static class EntityAdapterExtensions
{
    public static List<IMyEntity> ToIMyEntityList(this List<EntityAdapter<IYourEntity, IMyEntity>> lstEntityAdapters)
    {
        return lstEntityAdapters.ConvertAll(e => e.CastToMyEntityType());
    }
    
    public static List<EntityAdapter<IYourEntity, IMyEntity>> ToEntityAdapterList(this List<IYourEntity> lstYourEntities)
    {
        return lstYourEntities.Select(e => 
        {                    
            switch (e)
            {
                case YourEntity1 yourEntity1:
                    return new EntityAdapter<IYourEntity, IMyEntity>(yourEntity1, new MyEntity1Converter());
                default:
                    throw new NotSupportedException("You forgot to map " + e.GetType());
            }
        }).ToList();
    }
}

第一个很容易理解,但第二个绝对需要维护。由于已经解释过的原因,我放弃了泛型,所以剩下要做的唯一一件事就是从实际的实体类型开始创建 EntityAdapters。

这里是fiddle

这可能有点争议,但也许换一种方式更好?

首先,这更多是为了我,我建议使用更容易理解的术语,所以我会使用 'source' 和 'dest' 而不是 'your' 和 'my' .

其次我想知道是否需要泛型路由?我假设(我可能是错的)对于来自第三方程序集的每个 classes,您都有一个特定的 class 用于将其转换为。因此,也许在您的目标 class.

中使用构造函数覆盖可以更轻松地实现这一点
// third party class example
public class SourceClass
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// the destination class in your project
public class DestClass
{
    public int Id { get; set; }
    public string Name { get; set; }
    public DateTime CreatedOn { get; set; }

    // default constructor
    public DestClass()
    {
    }

    // conversion constructor
    public DestClass(SourceClass source)
    {
        Id = source.Id;
        Name = source.Name;
        CreatedOn = DateTime.UtcNow;
    }
}

这样您可以使用以下方式转换单个实例:

// source being an instance of the third-party class
DestClass myInstance = new DestClass(source);

并且您可以使用 LINQ 转换列表:

// source list is IList<SourceClass>
IList<DestClass> myList = sourceList.Select(s => new DestClass(s)).ToList();

如果您愿意,可以为您的转化实施扩展。这又不是通用的,因为每个 class 配对都需要一个,但由于它是为每个 class 编写转换器的替代方法,因此总体代码会更少。

public static class SourceClassExtensions
{
    public static DestClass ToDest(this SourceClass source)
        => new DestClass(source);

    public static IList<DestClass> ToDest(this IList<SourceClass> source)
        => source.Select(s => new DestClass(s)).ToList();
}

如果您仍然想要一些通用的东西,那么您将需要为每个 class 对提供一个转换器,实现一个合适的接口。然后我会推荐一个转换器工厂 class,您需要在其中将特定转换器注册到 class 中的字典中或通过依赖注入。如果你愿意,我可以进一步讨论这个问题,但我认为它会更复杂。

很抱歉在这里写的不是实际答案,

通常没有选项可以做到这一点

你必须为每个实体编写

public interface IConvertToMyEntity<TYourEntity, TMyEntity>
    where TYourEntity : class, IYourEntity, new()
    where TMyEntity : class, IMyEntity, new()
{
    TMyEntity ToMyEntity(TYourEntity yourEntity);
}

我从你的问题中看到了这段代码。

要看你改造后想做什么

你应该使用数据映射器

public class MapProfile : Profile
{
    public MapProfile()
    {
        CreateMap<TYourEntity , TMyEntity >();
        CreateMap<TMyEntity , TYourEntity >();
    }
}