代码之家  ›  专栏  ›  技术社区  ›  Andre

在泛型方法内调用特定实现

  •  1
  • Andre  · 技术社区  · 6 年前

    我目前正在尝试实现一个通用方法来将来自外部服务的DTO调整为我的服务模型,我遇到了一个问题。 首先让我把这个问题具体化。 假设外部服务将以下DTO返回给我的应用程序。

    public class ExampleDTO
    {
        public int Field1 { get; set; }
        public int Field2 { get; set; }
    }
    

    这是我的模型。

    public class ExampleModel
    {
        public int Field1 { get; set; }
        public int Field2 { get; set; }
    }
    

    如果我想将第一个类应用到我的模型中,我可以简单地编写以下方法:

    public ExampleModel Adapt(ExampleDTO source)
    {
        if (source == null) return null;
    
        var target = new ExampleModel()
        {
            Field1 = source.Field1,
            Field2 = source.Field2
        };
    
        return target;
    }
    

    现在假设当我们得到一个ExampleDTOs集合时,服务不是返回一个ICollection类型的集合,而是返回以下类:

    public class PagedCollectionResultDTO<T>
    {
       public List<T> Data { get; set; }
       public int Page     { get; set; }
       public int PageSize { get; set; }
       public int Total    { get; set; }
    }
    

    其中,exampledTo的列表出现在Data字段上,page number、page size和total number of records出现在其余字段上。

    我正在尝试实现一个泛型方法来使这个类适应我自己的模型,这个模型具有相同的结构。我想独立于数据字段的类型T来执行此操作。

    public class PagedCollectionResult<T>
    {
        public List<T> Data { get; set; }
        public int Page     { get; set; }
        public int PageSize { get; set; }
        public int Total    { get; set; }
    
        public PagedCollectionResult() => (Data, Page, PageSize, Total) = (new List<T>(), 0, 0, 0);
    }
    

    我尝试了以下方法,尝试将DTO分页结果调整为模型分页结果(T):

    public PagedCollectionResult<T> Adapt<S,T>(PagedCollectionResultDTO<S> source) 
    {
        if (source == null) return null;
    
        var target = new PagedCollectionResult<T>();
    
        foreach (var item in source.Data)
            target.Data.Add(this.Adapt(item));
    
        target.Page     = source.Page;
        target.PageSize = source.PageSize;
        target.Total    = source.Total;
    
        return target;
    }
    

    问题是我在这一行中遇到了一个错误:

    target.Data.Add(this.Adapt(item));
    

    它说它不能把S转换成ExampleDTO。 如果我在Adapt上对ExampleDTO/ExampleModel设置了限制,那么这就不再是通用的了。 是否有方法为特定类型调用Adapt(item)方法?

    这是我的完整类型适配器:

    public class TypeAdapter
    {
        public PagedCollectionResult<T> Adapt<S,T>(PagedCollectionResultDTO<S> source) 
        {
            if (source == null) return null;
    
            var target = new PagedCollectionResult<T>();
    
            foreach (var item in source.Data)
                target.Data.Add(this.Adapt(item));
    
            target.Page     = source.Page;
            target.PageSize = source.PageSize;
            target.Total    = source.Total;
    
            return target;
        }
    
        public ExampleModel Adapt(ExampleDTO source)
        {
            if (source == null) return null;
    
            var target = new ExampleModel()
            {
                Field1 = source.Field1,
                Field2 = source.Field2
            };
    
            return target;
        }
    }
    
    4 回复  |  直到 6 年前
        1
  •  2
  •   Kostas Dafnomilis    6 年前

    据我所知,解决方案分为两部分,这两部分应该相互独立地工作。

    A部分:对象转换的通用模式

    创建一个接口,由需要从外部DTO调整的所有模型实现。

    public interface IExampleModel<S>
    {
        void Adapt(S source);
    }
    

    这个接口只需要一个 Adapt 方法和泛型类型 S 描述要适应的类型。

    现在为每个要从另一种类型进行调整的模型构建一个类。

    public class ExampleModel : IExampleModel<ExampleDTO>
    {
        public int Field1 { get; set; }
        public int Field2 { get; set; }
    
        public void Adapt(ExampleDTO source)
        {
            Field1 = source.Field1;
            Field2 = source.Field2;
        }
    }
    

    TypeAdapter类:

    public class TypeAdapter
    {
        public PagedCollectionResult<T> Adapt<S,T>(PagedCollectionResultDTO<S> source) 
           where T: IExampleModel<S>, new()
        {
            var target = new PagedCollectionResult<T>();
            target.Page = source.Page;
            target.Page = source.PageSize;
            target.Total = source.Total;
            target.Data = AdaptList<S,T>(source.Data).ToList();
    
            return target;
        }
    
        protected IEnumerable<T> AdaptList<S,T>(IEnumerable<S> sourceList) 
           where T : IExampleModel<S>, new()
        {
            foreach (S sourceItem in sourceList)
            {
                T targetItem = new T();
                targetItem.Adapt(sourceItem);
                yield return targetItem;
            }
        }
    }
    

    注1 :的 PagedCollectionResult 这种方法不需要构造函数。

    注2 :我选择将 适应 方法来满足 开闭原理 . 这样,当您希望扩展该解决方案以接受第二类型转换时,您不需要修改任何现有的类(即 TypeModifier ),但添加一个新类。

    public class ExampleModel2 : IExampleModel<ExampleDTO2>
    {
        public int Field1 { get; set; }
        public int Field2 { get; set; }
    
        public void Adapt(ExampleDTO2 source)
        {
            Field1 = source.Field1;
            Field2 = source.Field2;
        }
    }
    

    添加这个类也将扩展解决方案以包含这个转换。

    当然,如果它更适合您的应用程序,您可以选择自己的方式。

    B部分:服务电话

    据我所知,应用程序没有办法提取目标类型,只给定源类型。你必须在服务电话中提供。你对你的服务电话没有描述,所以我给你一些提示。

    如果您的服务电话是这样的,它将工作:

        public void ServiceCall<S,T>(PagedCollectionResultDTO<S> sourceCollection)
            where T : IExampleModel<S>, new()
        {
            var typeAdapter = new TypeAdapter();
            var targetCollection = typeAdapter.Adapt<S,T>(sourceCollection);
        }
    

    如果无法在服务调用中传递T类型,则可以使用If子句正确定义目标类型:

        public void ServiceCall<S>(PagedCollectionResultDTO<S> sourceCollection)
        {
            var typeAdapter = new TypeAdapter();
            if (typeof(S) == typeof(ExampleDTO))
            {
                var targetCollection = typeAdapter.Adapt<ExampleDTO, ExampleModel>(sourceCollection as PagedCollectionResultDTO<ExampleDTO>);
            }
            else if(typeof(S) == typeof(ExampleDTO2))
            {
                var targetCollection = typeAdapter.Adapt<ExampleDTO2, ExampleModel2>(sourceCollection as PagedCollectionResultDTO<ExampleDTO2>);
            }
        }
    
        2
  •  1
  •   Code Name Jack    6 年前

    你有两个选择, 一。通过反射生成模型和Dto的属性列表。然后匹配他们的类型。

    class AdapterHelper<T1, T2>
    {
        public T1 Adapt(T2 source)
        {
            T1 targetItem = Activator.CreateInstance<T1>();
            var props = typeof(T1).GetProperties();
            var targetProps = typeof(T2).GetProperties();
            foreach (var prop in props)
            {
                foreach (var targetProp in targetProps)
                {
                    if (prop.Name == targetProp.Name)
                    {
                        targetProp.SetValue(targetItem, prop.GetValue(source));
                        //assign
    
                    }
                }
            }
            return targetItem;
        }
    }
    

    2.使用 Automapper

        3
  •  1
  •   Grax32    6 年前

    因为您正在实现一个泛型方法,所以您需要实现一个泛型方法来将S转换为T(参见其他答案),或者您需要传入转换函数。

    public PagedCollectionResult<T> Adapt<S, T>(PagedCollectionResultDTO<S> source, Func<S, T> adapt)
    {
        if (source == null) return null;
    
        var target = new PagedCollectionResult<T>();
    
        foreach (var item in source.Data)
            target.Data.Add(adapt(item));
    
        target.Page = source.Page;
        target.PageSize = source.PageSize;
        target.Total = source.Total;
    
        return target;
    }
    

    下面是调用上述方法的示例代码。

    static void Main(string[] args)
    {
        var src = new PagedCollectionResultDTO<ExampleDTO>();
        src.Data = new List<ExampleDTO>{
            new  ExampleDTO{ Field1 = 1, Field2 = 2 }
        };
        var adapter = new TypeAdapter();
        var result = adapter.Adapt(src, AdaptExampleDTO);
    }
    
    public static ExampleModel AdaptExampleDTO(ExampleDTO source)
    {
        if (source == null) return null;
    
        var target = new ExampleModel()
        {
            Field1 = source.Field1,
            Field2 = source.Field2
        };
    
        return target;
    }
    
        4
  •  0
  •   Andre    6 年前

    谢谢你们的回答,他们帮助我朝着正确的方向前进。 我在运行时使用反射来解决正确的自适应方法。 多亏了你,我学到了一些思考的东西。 我正在分享解决方案,希望我也能有所回报。 这就是我的结局。

    public PagedCollectionResult<T> Adapt<S, T>(PagedCollectionResultDTO<S> source)
    {
        if (source == null)
        {
            return null;
        }
    
        var target = new PagedCollectionResult<T>();
    
        // Get the type of S at runtime
        Type[] types = { typeof(S) };
    
        // Find the Adapt method on the TypeAdapter class that accepts an object of type S
        var adaptMethod = typeof(TypeAdapter).GetMethod("Adapt", types);
    
        foreach (var item in source.Data)
        {
            // for each item call the adapt method previously resolved and pass the item as parameter
            var parameters = new object[] { item };
            target.Data.Add((T)adaptMethod.Invoke(this, parameters));
        }
    
        target.Page = source.Page;
        target.PageSize = source.PageSize;
        target.Total = source.Total;
    
        return target;
    }