【问题标题】:How to create an extension method with the same signature but different generic type?如何创建具有相同签名但不同泛型类型的扩展方法?
【发布时间】:2011-09-15 12:25:33
【问题描述】:

我有以下扩展方法:

public static class QueryableOptionalDateRangeExtensions
{
    public static IQueryable<T> StartsFrom<T>(this IQueryable<T> query, DateTime date)
        where T : IOptionalDateRange // Might also be IRequiredDateRange
    {
        return query.Where(obj => obj.Start >= date);
    }

    public static IQueryable<T> StartsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IOptionalDateRange // Might also be IRequiredDateRange
    {
        return query.Where(obj => obj.Start < date);
    }

    public static IQueryable<T> EndsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IOptionalDateRange
    {
        return query.Where(obj => obj.End <= date);
    }
}

public static class QueryableRequiredDateRangeExtensions
{
    public static IQueryable<T> StartsFrom<T>(this IQueryable<T> query, DateTime date)
        where T : IRequiredDateRange
    {
        return query.Where(obj => obj.Start >= date);
    }

    public static IQueryable<T> StartsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IRequiredDateRange
    {
        return query.Where(obj => obj.Start < date);
    }

    public static IQueryable<T> EndsUntil<T>(this IQueryable<T> query, DateTime date)
        where T : IRequiredDateRange
    {
        return query.Where(obj => obj.End <= date);
    }
}

但这不起作用,因为由于某种原因它无法从 T 的类型推断出重载(尽管在我看来这是可能的)。
可以做些什么来解决这个问题?

编辑:
这是 IDateRange 接口:

public interface IDateRange<TStart, TEnd>
{
    TStart Start { get; set; }
    TEnd End { get; set; }
}

它只是指定类有开始和结束。 现在我希望它指定对象是否具有可选的日期范围(开始和结束都可以为空)或所需的日期范围(两者都是值类型),但相同的扩展方法必须适用于两者,我真的不想指定 Start 和 End 属性的类型。

【问题讨论】:

  • @Daniel Hilgarth:已经完成,但 IDateRange 有两个用于 Start 和 End 属性的通用参数(它们可能为空或不为空),然后您无法将 T 与日期时间进行比较。
  • 请将其作为对我的回答的评论发布 - 我会在那里回答您的评论。
  • 你应该考虑一下那个设计。你的IDateRange 说这是一个日期范围,但我可以在里面放任何东西,例如我可以创建一个IDateRange&lt;Guid, HttpContext&gt;...
  • 您还没有展示实际发生的情况。一段简短但完整的代码显示单个方法声明(而不是 4 个)以及尝试使用它的一些东西会很有帮助。
  • 好的,对我来说,需要重新考虑设计(请参阅我更新的答案)。将它开放给任何类型只是为了支持可选或必需(是/否标志)感觉不对。

标签: c# .net generics extension-methods


【解决方案1】:

不清楚您所说的“不起作用”是什么意思。您是否期望重载解决方案(包括查找扩展方法)考虑约束?如果是这样,那不会发生 - 我已经写了一个 blog post 来详细介绍这个。

这可能就是您尝试的方法不起作用的原因 - 尽管您没有给出示例,因此很难确定。至于如何 fix 它 - 我建议使用不同的方法名称,例如MaybeStartsFrom 用于可选范围。如果你能给出一个简短但完整的例子来说明你想要实现的目标,那将有所帮助......

【讨论】:

    【解决方案2】:

    IRequiredDateRangeIOptionalDateRange 都继承自IDateRange 并将StartEnd 属性放在那里。否则无法实现您正在尝试的内容,因为“通用方法类型推断故意从约束中进行任何推论”,请参阅here

    【讨论】:

      【解决方案3】:

      首先,在QueryableOptionalDateRangeExtensions的方法中,T总是“IOptionalDateRange”,为什么要先泛型呢?

      其次,IOptionalDateRange 接口似乎只是扩展了 IRequiredDateRange 接口(我猜是因为你写的 IOptionalDateRange 也可能是 IRequiredDateRange)。这意味着,如果一个对象实现了 IOptionalDateRange,它也实现了 IRequiredDateRange。您希望编译器如何区分?

      所以解决这个问题,接口不应该相互继承,而是可以共享一个公共基类。

      【讨论】:

      • 因为 T 必须是具体类型才能在 IQueryable 中使用。 IOptionalDateRange 和 IRequiredDateRange 都继承自 IDateRange。我写道,约束同时匹配 IRequiredDateRange 和 IOptionalDateRange。
      【解决方案4】:

      如果您使用 .NET 4 并使用类(而不是结构)实现接口,则可以利用协方差并使方法非泛型。相反,你会有

      IQueryable<IOptionalDateRange> EndsUntil(this IQueryable<IOptionalDateRange> query, DateTime date)
      IQueryable<IRequiredDateRange> EndsUntil(this IQueryable<IRequiredDateRange> query, DateTime date)
      

      【讨论】:

      • 如果我在 .NET 4 上,这将是公认的答案。但是我不确定 NHibernate 会如何处理这个问题。我相信它仍然会导致错误。
      【解决方案5】:

      您可以将它们移动到单独的命名空间中,但这很糟糕。它很脆弱,因为更改命名空间可能会导致不同的行为(出乎意料)。另外,如果您需要在同一个文件中使用这两种方法(如 Daniel 所指出的),这将不起作用。


      好的,所以在阅读了您的更新之后,为什么不将IsDateOptional 属性添加到您的IDateRange 接口,将日期类型定义为DateTime,从而摆脱该接口的通用元素。现在不需要两个单独的接口IOptionalDateRangeIRequiredDateRange...,您可以将这两个扩展类替换为一个。

          public interface IDateRange
          {
              DateTime Start { get; set; }
              DateTime End { get; set; }
              bool IsDateOptional { get; }
          }
      
          public static class QueryableDateRangeExtensions
          {
              public static IQueryable<T> StartsFrom<T>(this IQueryable<T> query, DateTime date)
                  where T : IDateRange
              {
                  return query.Where(obj => obj.Start >= date);
              }
      
              public static IQueryable<T> StartsUntil<T>(this IQueryable<T> query, DateTime date)
                  where T : IDateRange
              {
                  return query.Where(obj => obj.Start < date);
              }
      
              public static IQueryable<T> EndsUntil<T>(this IQueryable<T> query, DateTime date)
                  where T : IDateRange
              {
                  return query.Where(obj => obj.End <= date);
              }
          }
      

      【讨论】:

      • 这是扩展 IQueryable 并使日期范围比较更易于理解的方式。
      • “您可以将它们移动到单独的命名空间中”。一旦他需要在同一个文件中,这就会失败。
      • @Daniel Hilgarth:这就是为什么我说这很糟糕 :)
      • 我知道你说过。但你没有说为什么;-)
      猜你喜欢
      • 1970-01-01
      • 2023-02-14
      • 1970-01-01
      • 2022-11-02
      • 1970-01-01
      • 1970-01-01
      • 2013-02-28
      • 2021-11-29
      • 1970-01-01
      相关资源
      最近更新 更多