0

我有这样的定义:

 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 是什么类型。因此,我会认为通过定义 EI 也会定义 D。

但是 IQueryable.ReturnDTO() 要求像这样指定泛型参数:

 IQueryable.ReturnDTO<someEntity, someDTO>();

这显然是丑陋的。

我尝试将其设置IQueryable<E>为 this IQueryable<BaseObjectWithDTO<D, int>>,但是这与 func 的 in 没有任何关系,因为它不会采用由 Generic Parameter 推断的类型IQuerayble

var projection = Expression.Lambda<Func<E, D>>(memberInitExpression, itemParam);

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

4

3 回答 3

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

如果您希望稍微不那么神秘,您可以制作 dummy parameter D dummy,然后例如myQueryable.ReturnDTO(default(SomeDTO))(这里使用default作为获取 null 或默认值的明确方法,而无需引用该类型的变量/字段/属性)如果您更喜欢。

于 2013-11-14T21:24:15.790 回答
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 不鼓励这种事情)。但无论如何,您的设计将如下所示:

public abstract class BaseDTO<T> where T : BaseDTO<T>, new()
{ 
    public static T Empty { get { return new T(); } }
}

public class ProductDTO : BaseDTO<ProductDTO> { }

ReturnDTO如果您想强制执行一个不变量,即所有 DTO 都是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));

如果您使用的是 anIEnumerable而不是 anIQueryable可能是

Product.QuerySource.Select(ProductDTO.From);

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

于 2013-11-14T22:15:29.680 回答
1

我不认为这是可能的,因为你目前已经设计了它,这个 MSDN页面指出在这种情况下类型推断是不可能的:

类型推断的相同规则适用于静态方法和实例方法。编译器可以根据你传入的方法参数来推断类型参数;它不能仅从约束或返回值推断类型参数。

这意味着您必须将您类型的参数传递给此方法,以便编译器能够推断类型。

于 2013-11-14T21:24:04.203 回答