我有这样的定义:
public static IQueryable<D> ReturnDTO<E, D>(this IQueryable<E> query)
where D : BaseDTO, new()
where E : BaseObjectWithDTO<D, int>
{
//expression tree code to convert
}
BaseObjectWithDTO定义了它的DTO类型。因此,我认为通过定义E我也会定义D。
但IQueryable.ReturnDTO()要求如下指定泛型参数:
IQueryable.ReturnDTO<someEntity, someDTO>();
这显然是UGLY。
我尝试将此IQueryable<E>
替换为此IQueryable<BaseObjectWithDTO<D, int>>
,但之后没有任何内容作为func的内容,因为它不会采用{{1}的通用参数所推断的类型}:
IQuerayble
关于如何让它不需要每次传递类型的想法?
答案 0 :(得分:3)
不幸的是,C#的通用类型推理系统并不像它那样强大。如果您包含一个涉及D
的参数,那么它可以推断它。例如......
public static IQueryable<D> ReturnDTO<E, D>(this IQueryable<E> query,
IQueryable<BaseObjectWithDTO<D, int>> dummy)
// now you can do...
myQueryable.ReturnDTO(myQueryable);
// instead of
myQueryable.ReturnDTO<BaseObjectWithDTO<BaseDTO, int>, BaseDTO>();
令人困惑并且可以说是两次传递相同变量的糟糕设计,但它比必须明确指定类型或使用反射或其他运行时技术来提取类型(当不需要时)更好(恕我直言)。
由于您实际上并不打算使用dummy
参数,因此只要类型正确,值无关紧要,因此您最终仍可以使用此参数查询链的例子,例如即使您传入两个不同的IQueryable
s。
var result = otherQueryable.Where(...).ReturnDTO(otherQueryable);
如果您希望稍微不那么神秘,可以制作虚拟参数D dummy
,然后例如myQueryable.ReturnDTO(default(SomeDTO))
(如果您愿意,可以使用default
作为获取null或默认值的明确方法,而无需引用该类型的变量/ field / property)。
答案 1 :(得分:1)
我认为这不可能,因为您目前已经设计了这个,这个MSDN page表明在这种情况下无法进行类型推断:
类型推断的相同规则适用于静态方法和实例 方法。编译器可以根据它推断出类型参数 传入的方法参数;它不能仅推断类型参数 来自约束或返回值。
这意味着您必须将类型的参数传递给此方法,以便编译器能够推断出类型。
答案 2 :(得分:1)
您必须指定类型,但不必在q.Return<E,D>()
中明确指定。有一些方法可以传递指定类型参数,以便可以隐式推断它。为此,您需要稍微更改签名。
public static IQueryable<D> ReturnDTO<E, D>(this IQueryable<E> query, D dtoTypeExample = default(D))
where D : BaseDTO, new()
where E : BaseObjectWithDTO<D, int>
{
//expression tree code to convert
}
现在,即使有一个默认参数,编译器也无法获取它,除非你传入一些参数。你传入的东西不必以任何其他方式被该方法使用。例如,假设您有:
public class ProductDTO : BaseDTO {
public static ProductDTO Empty { get { return new ProductDTO(); } }
}
public class Product : BaseObjectWithDTO<ProductDTO,int> {
public static IQueryable<Product> QuerySource { get; set; }
}
然后你可以打电话:
ProductDTO dto = Product.QuerySource.ReturnDTO(ProductDTO.Empty);
我不是说这一定是个好主意,但你可以做到。此外,它不必是您传入的实际类型 - 您只需要传递足够接近编译器的内容以推断出预期的类型。例如,您可以使用以下签名:
public static IQueryable<D> ReturnDTO<E, D>(this IQueryable<E> query, Func<D,D> dtoIdentity = default(Func<D,D>))
where D : BaseDTO, new()
where E : BaseObjectWithDTO<D, int>
{
//expression tree code to convert
}
然后如果你有:
public class ProductDTO : BaseDTO {
public static ProductDTO Identity(ProductDTO dto){ return dto; };
}
public class Product : BaseObjectWithDTO<ProductDTO,int> {
public static IQueryable<Product> QuerySource { get; set; }
}
然后你可以打电话:
ProductDTO dto = Product.QuerySource.ReturnDTO(ProductDTO.Identity);
这可能会让某些人更具语义感,但这有点主观。再一次,我不推荐这个,只是说你可以做到。如果您决定这样做,可能会为您节省一些自我参考通用基础(警告: Eric Lippert discourages this kind of thing)。但无论如何,你的设计会如下:
public abstract class BaseDTO<T> where T : BaseDTO<T>, new()
{
public static T Empty { get { return new T(); } }
}
public class ProductDTO : BaseDTO<ProductDTO> { }
如果要强制使用所有DTO作为ReturnDTO
的自引用导数与公共无参数构造函数的不变量,您还可以将类型约束添加到BaseDTO<T>
方法。但是,如果您正在尝试编写通常被视为优秀代码的内容,您可能不会执行任何操作,如果您认为它很难看,您只需闭上眼睛并明确使用参数约束。
还有一件我想到过的事情,不会那么令人不悦。考虑Queryable.Cast<T>
和Queryable.OfType<T>
方法。它们采用非通用IQueryable
参数,但返回IQueryable<T>
。如果你确保验证你对参数的假设,它可能足够干净。然后你会失去一些编译时类型安全性。您需要拥有BaseObjectWithDTO
将继承的BaseObjectWithDTO<TData,TKey>
等非通用基础。您的方法将如下所示:
public static IQueryable<D> ReturnDTO<D>(this IQueryable<BaseObjectWithDTO> query)
where D : BaseDTO, new()
{
if(query == null) throw new ArgumentNullException("query");
if( !typeof(BaseObjectWithDTO<D,int>) .IsAssignableFrom(query.GetType().GetGenericParameters()[0]))
throw new ArgumentOutOfRangeException("query");
//expression tree code to convert
}
那并不可怕。但它也可能不太好。它可能比我列出的其他选项更好,但谁知道呢。
可能对你有用的另一种语法刚刚发生在我身上,但它也非常滥用。想象一下,你确实走了BaseDTO<T> where T : BaseDTO<T>,new()
路线。您可以在该类型上声明方法以提取DTO可查询。这就是我的想法:
public abstract class BaseDTO<T>
where T : BaseDTO<T>, new()
{
public static T From(BaseObjectWithDTO<T,int> entity){
if(entity == null) throw new ArgumentNullException("entity");
//expression tree code to convert
}
}
那么你不再需要那个方法ReturnDTO
作为扩展方法,因为你有正常的LINQ。如果你愿意,你仍然可以将它添加为语法糖,但是使用这些语义,你的调用最终看起来像:
IQueryable<ProductDTO> dtoQuery = from entity in Product.QuerySource select ProductDTO.From(entity);
也可以写成
Product.QuerySource.Select(entity => ProductDTO.From(entity));
如果您使用的是IEnumerable
而不是IQueryable
,则
Product.QuerySource.Select(ProductDTO.From);
请记住:我所说的只是你可以这样做。我不是说你应该。