在泛型方法中调用特定实现
Calling a particular implementation inside a generic method
我目前正在尝试实现一种通用方法,以将来自外部服务的 DTO 调整为我的服务模型,而我 运行 遇到了问题。
首先让我将问题背景化。
假设外部服务 returns 以下 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; }
}
如果我想将第一个 class 应用到我的模型中,我可以简单地编写以下方法:
public ExampleModel Adapt(ExampleDTO source)
{
if (source == null) return null;
var target = new ExampleModel()
{
Field1 = source.Field1,
Field2 = source.Field2
};
return target;
}
现在假设当我们获取 ExampleDTO 的集合时,服务不只是返回 ICollection 类型的集合,returns 如下 class:
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 列表出现在数据字段中,页码、页面大小和记录总数出现在其余字段中。
我正在尝试实现一个通用方法来使这个 class 适应我自己的模型,它具有相同的结构。我想独立于数据字段的类型 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;
}
}
你有两个选择,
1. 通过反射生成Model和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.Use Automapper
据我了解,该解决方案分为两部分,它们应该彼此独立工作。
A 部分:对象转换的通用模式
创建一个由所有需要从外部 DTO 改编的模型实现的接口。
public interface IExampleModel<S>
{
void Adapt(S source);
}
此接口仅需要一个 Adapt
方法,泛型类型 S
描述了要改编的类型。
现在为您要从其他类型改编的每个模型构建一个 class。
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 class:
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
方法放在每个模型 class 而不是 TypeAdapter 中以满足 开闭原则。这样,当您想扩展解决方案以接受第二种类型转换时,您不会修改任何现有的 classes(即 TypeModifier
),而是添加一个新的 class.
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;
}
}
添加此 class 将扩展解决方案以包含此转换。
当然,如果更适合您的应用,您可以选择自己的方式。
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>);
}
}
因为你正在实现一个泛型方法,所以你需要实现一个将 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;
}
感谢大家的回复,他们帮助我找到了正确的方向。
我在运行时使用反射来解析正确的 adapt 方法。
多亏了你,我学到了一些关于反思的知识。
我正在分享解决方案,希望我也能回馈一些东西。
这就是我最终得到的结果。
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;
}
我目前正在尝试实现一种通用方法,以将来自外部服务的 DTO 调整为我的服务模型,而我 运行 遇到了问题。 首先让我将问题背景化。 假设外部服务 returns 以下 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; }
}
如果我想将第一个 class 应用到我的模型中,我可以简单地编写以下方法:
public ExampleModel Adapt(ExampleDTO source)
{
if (source == null) return null;
var target = new ExampleModel()
{
Field1 = source.Field1,
Field2 = source.Field2
};
return target;
}
现在假设当我们获取 ExampleDTO 的集合时,服务不只是返回 ICollection 类型的集合,returns 如下 class:
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 列表出现在数据字段中,页码、页面大小和记录总数出现在其余字段中。
我正在尝试实现一个通用方法来使这个 class 适应我自己的模型,它具有相同的结构。我想独立于数据字段的类型 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;
}
}
你有两个选择, 1. 通过反射生成Model和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.Use Automapper
据我了解,该解决方案分为两部分,它们应该彼此独立工作。
A 部分:对象转换的通用模式
创建一个由所有需要从外部 DTO 改编的模型实现的接口。
public interface IExampleModel<S>
{
void Adapt(S source);
}
此接口仅需要一个 Adapt
方法,泛型类型 S
描述了要改编的类型。
现在为您要从其他类型改编的每个模型构建一个 class。
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 class:
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
方法放在每个模型 class 而不是 TypeAdapter 中以满足 开闭原则。这样,当您想扩展解决方案以接受第二种类型转换时,您不会修改任何现有的 classes(即 TypeModifier
),而是添加一个新的 class.
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;
}
}
添加此 class 将扩展解决方案以包含此转换。
当然,如果更适合您的应用,您可以选择自己的方式。
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>);
}
}
因为你正在实现一个泛型方法,所以你需要实现一个将 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;
}
感谢大家的回复,他们帮助我找到了正确的方向。 我在运行时使用反射来解析正确的 adapt 方法。 多亏了你,我学到了一些关于反思的知识。 我正在分享解决方案,希望我也能回馈一些东西。 这就是我最终得到的结果。
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;
}