【问题标题】:Generic Extension Method with Concrete Class Override具有具体类覆盖的通用扩展方法
【发布时间】:2023-03-12 08:14:01
【问题描述】:

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

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

....但是我不知道如何制作一个通用的扩展方法,它将调用具体的扩展方法来进行每个类的特定转换。

将我的代码的一些主要方面放在下面,但您可以充分利用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));
    }
}

【问题讨论】:

  • 您说YourEntitys 来自第三方DLL,但它们似乎通过IConvertToMyEntity 接口与MyEntitys 耦合。你能解释一下你能控制什么吗?
  • @MarcoLuzzara 是的,它们来自第三方。我试图创建 IConvertToMyEntity 接口来尝试解决这个问题,但这种方法可能不正确。我的目标是能够在通用方法中执行此操作,以转换我从该第三方检索到的任何第三方对象列表。即List&lt;IYourEntity&gt; lstYourEntities.ToMyEntities(),它将调用特定于实体类型的转换方法来获取我的实体列表。
  • 只需采取简单的方法并使用自动映射器。无论您需要使用相同的属性名称还是为不匹配的属性名称创建手动映射,如果是这种情况,那么只需使用自动映射器就可以了
  • @johnny5 是的,我可以使用 AutoMapper 来完成复制数据的具体细节,但这里的难点在于如何创建一个通用方法来知道要使用哪些映射。
  • crichavin,我不明白为什么这很困难。 1. 你知道你要适应和适应的类型。例如,在您的示例中,YourEntity1, MyEntity1 只需输入 IAdaptable&lt;YourEntity,MyEntity&gt;,只要您需要它,只需调用 convert。

标签: c# generics extension-methods c#-7.0


【解决方案1】:

由于实现IYourEntity 的类来自第三方并且不受您的控制,因此您无法在这些类上实现自己的IConvertToMyEntity&lt;T1, T2&gt; 接口。

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

假设你有 3 个类实现了IYourEntity 接口;
例如YourCustomerYourOrderYourProduct

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

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

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

对于需要自定义转换的实体,您可以针对这些特定的源类类型设置此扩展方法的重载。
以下是 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 的 inbetween 可以调用适当的 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)

【讨论】:

  • 这看起来很有希望......现在更详细地查看它。
  • 谢谢。这看起来很棒,正是我想要的。我不知道你可以有这样的覆盖。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    我仍然不完全清楚如何让第三方类轻松实现IConvertToMyEntity。假设您这样做只是为了向我们展示您的实际目标,那么您应该非常小心在 Main 中尝试完成的任务。

    如果你使用List&lt;IYourEntity&gt;,你只能使用接口中定义的方法和属性,除非你知道你在用特定的演员做什么。对List&lt;IYourEntity&gt;List&lt;IMyEntity&gt; 的需求极大地限制了My 类和Your 类之间的自定义映射器的实现。这里有一个可能的解决方案:

    正如我所说,我没有更改 Your 类:

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

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

    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 类的 List 上扩展方法,则不能这样做。还尝试使用协变和逆变接口,但 C# 不允许您在此实现中使用它们。

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

    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 类给出的。优点是您可以通过调用 CastToMyEntityType 或显式运算符重载将此 EntityAdapter 转换为 My 类的实例。

    痛苦的部分在于扩展方法:

    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

    【讨论】:

      【解决方案4】:

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

      首先,为了我的利益,我建议使用更容易理解的术语,所以我会使用“source”和“dest”来代替“your”和“my”。

      其次,我想知道泛型路线是否必要?我假设(我可能错了)对于来自第三方程序集的每个类,您都有一个特定的类可以转换为。所以也许这可以通过在你的目标类中重写构造函数来更容易地实现。

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

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

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

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

      【讨论】:

      • 是的,我更喜欢你使用 SourceDestination :) 我想要泛型的原因是为了简化代码并尝试消除基于 Source 的 switch 语句来执行其余的映射。
      【解决方案5】:

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

      一般来说没有办法这样做

      你必须为每个实体写

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-10
        相关资源
        最近更新 更多