在通用方法内调用特定的实现

时间:2018-08-07 19:18:27

标签: c# .net generics generic-programming

我目前正在尝试实现一种通用方法,以将来自外部服务的DTO调整为我的Servisse模型,但遇到了问题。 首先,让我将问题具体化。 假设外部服务将以下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;
}

现在让我们说,当我们获得ExampleDTO的集合时,该服务将返回以下类,而不仅仅是返回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; }
}

“示例” DTO的列表位于“数据”字段中,页码,页大小和记录总数位于其余字段中。

我正在尝试实现一种通用方法,以使此类适合我自己的模型,该模型具有相同的结构。我想独立于数据字段的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分页结果(S)调整为模型分页结果(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)方法?

这是我完整的TypeAdapter:

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 个答案:

答案 0 :(得分:2)

据我了解,该解决方案分为两部分,应该彼此独立工作。

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 :我选择将Adapt方法放在每个模型类中,而不是在TypeAdapter中,以满足开闭原理。这样,当您想扩展解决方案以接受第二种类型转换时,无需修改任何现有类(即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>);
        }
    }

答案 1 :(得分:1)

您在这里有两种选择, 1.通过反射生成模型和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

答案 2 :(得分:1)

由于要实现通用方法,因此需要实现将S转换为T的通用方法(请参见其他答案),或者需要传递转换函数。

(a, s, i, j, k)

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

smalltofront a s i j k

答案 3 :(得分:0)

谢谢大家的回应,他们帮助我朝着正确的方向前进。 我在运行时使用反射来解决正确的适应方法。 感谢您,我学到了一些有关反射的知识。 我正在分享解决方案,希望我也能有所作为。 这就是我最终的目的。

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