如何定义返回子泛型类型的泛型扩展方法

时间:2013-11-14 21:15:37

标签: c# generics lambda

我有这样的定义:

 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

关于如何让它不需要每次传递类型的想法?

3 个答案:

答案 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);

请记住:我所说的只是你可以这样做。我不是说你应该。

相关问题