【问题标题】:No type inference with generic extension method没有使用泛型扩展方法进行类型推断
【发布时间】:2011-08-24 05:57:54
【问题描述】:

我有以下方法:

public static TEventInvocatorParameters Until
    <TEventInvocatorParameters, TEventArgs>(this TEventInvocatorParameters p,
                                            Func<TEventArgs, bool> breakCond)
    where TEventInvocatorParameters : EventInvocatorParameters<TEventArgs>
    where TEventArgs : EventArgs
{
    p.BreakCondition = breakCond;
    return p;
}

还有这个类

public class EventInvocatorParameters<T>
    where T : EventArgs
{
    public Func<T, bool> BreakCondition { get; set; }
    // Other properties used below omitted for brevity.
}

现在,我有以下问题:

  1. 此扩展方法适用于所有类型,甚至 string
  2. 我不能写new EventInvocatorParameters&lt;EventArgs&gt;(EventABC).Until(e =&gt; false); 它告诉我“方法的类型参数......无法从用法中推断出来。”

我不能像这样使用泛型类型参数吗?您将如何解决这个问题?
重点:我需要这两个泛型参数,因为我需要返回调用此扩展方法的相同类型。


更广泛的图片(回答问题不是必需的!):
我正在尝试创建一个流畅的界面来调用事件。基础是这个静态类:

public static class Fire
{
   public static void Event<TEventArgs>(
       ConfiguredEventInvocatorParameters<TEventArgs> parameters)
    where TEventArgs : EventArgs
    {
        if (parameters.EventHandler == null)
        {
            return;
        }

        var sender = parameters.Sender;
        var eventArgs = parameters.EventArgs;
        var breakCondition = parameters.BreakCondition;

        foreach (EventHandler<TEventArgs> @delegate in 
                 parameters.EventHandler.GetInvocationList())
        {
            try
            {
                @delegate(sender, eventArgs);
                if (breakCondition(eventArgs))
                {
                    break;
                }
            }
            catch (Exception e)
            {
                var exceptionHandler = parameters.ExceptionHandler;
                if (!exceptionHandler(e))
                {
                    throw;
                }
            }
        }
    }
}

为确保只能使用完全配置的参数调用此方法,它只接受派生自EventInvocatorParameters&lt;T&gt;ConfiguredEventInvocatorParameters&lt;T&gt;

public class ConfiguredEventInvocatorParameters<T>
    : EventInvocatorParameters<T>
    where T : EventArgs
{
    public ConfiguredEventInvocatorParameters(
        EventInvocatorParameters<T> parameters, object sender, T eventArgs)
        : base(parameters)
    {
        EventArgs = eventArgs;
        Sender = sender;
    }

    public T EventArgs { get; private set; }
    public object Sender { get; private set; }

}

以下是有效的调用:

Fire.Event(EventName.With(sender, eventArgs));
Fire.Event(EventName.With(sender, eventArgs).Until(e => e.Cancel));
Fire.Event(EventName.Until(e => e.Cancel).With(sender, eventArgs));

以下内容无效:

// no sender or eventArgs have been specified, i.e. missing call to With(...)
Fire.Event(EventName.Until(e => e.Cancel));

为了完成这项工作,存在名为 With 的扩展方法,它们接受 EventHandler&lt;TEventArgsTEventInvocatorParameters 并返回 ConfiguredEventInvocatorParameters&lt;TEventArgs&gt;With 之后的所有调用现在还需要返回 ConfiguredEventInvocatorParameters&lt;TEventArgs&gt; 类型,否则有效调用的第二个示例(以 Until 结尾)将不起作用。
如果您对 API 有任何想法,请告诉我。但是,我想避免以下三件事:

  • 只有在参数未完全配置时才会在运行时失败
  • 创建类似 EventName.With(...).Until(...).Fire() 的逆语法
  • 使用臭名昭著的Do 方法开始事情:Fire(EventName).With(...).Until(...).Do();

【问题讨论】:

    标签: c# .net generics extension-methods fluent-interface


    【解决方案1】:

    2020 年 11 月更新:下面的原始答案写于 2011 年;在最近的 C# 版本中,泛型方法类型推断、重载解决方案以及方法“最终验证”的完成方式发生了微小但重大的变化;这个答案,以及我原来的 MSDN 博客上关于它的存档文章的链接可能不再准确。此外,微软出于法律原因删除了原文章中的 cmets;在这些 cmets 中有大量的背景和讨论。我希望在某个时候有时间重新审视这篇文章,以澄清(1)今天的规则,(2)它们是如何改变的,以及(3)那些被删除的 cmets 中讨论的想法如何影响了这些决定,但这很多工作,我可能有一段时间都做不到。请记住,自 2012 年 11 月以来,我一直没有加入 C# 语言设计团队。


    通用方法类型推断故意从约束中进行任何推论。而是从 参数形参 进行推导,然后根据约束检查推导的类型参数。

    有关约束和方法签名的一些设计问题的详细讨论,包括数十人告诉我认为现有设计是明智的想法是错误的,请参阅我关于该主题的文章:

    https://docs.microsoft.com/en-gb/archive/blogs/ericlippert/constraints-are-not-part-of-the-signature

    【讨论】:

    • 我喜欢你回答的那一部分:“包括几十个人告诉我,我认为现有设计是明智的是错误的”! :-) 是否愿意提供实现我想要的替代方案?
    • 顺便说一句:我同意他们的看法。不想重新开始讨论:您所说的最佳匹配在技术上可能是这样,但直觉上并非如此。所有告诉你你错了的答案都应该足以证明这一点。在我看来,你的主要观点是无效的。没有猜测。我完全同意 David Nelson 在 2009 年 12 月 10 日下午 4:13 的回答。你对他的回答不再令人信服。主要问题是编译器推断出对该泛型方法无效的类型。
    • @Daniel:问题是不同的场景可以将你的直觉推向不同的方向。我坚持我的立场:C# 是一种语言,它会在出现问题时告诉你。当推导出的最佳选择违反约束时,它不会忽略问题并退回到更糟糕的选择。
    • 我知道你保持你的立场。所有这些论点都不能让你改变它,那我为什么要改变它呢?但是,如果您提供一个实际显示您认为问题所在的示例,您可能会提高理解。想象一下 C# 会以相反的方式实现,那么哪些场景会变得不直观?它会导致什么错误和问题?因为我 - 显然还有很多其他人 - 不明白这会如何“忽视问题”。
    • 我一直有直觉认为约束不会成为签名的一部分。如果它是错误的,我无法证明这种直觉会导致问题。我的直觉基于这样一个事实,即我希望“约束”一词暗示它将减少我可以进行的合法方法调用的数量,而不是改变这些方法调用的作用。我设想对针对特定情况未定义的方法使用约束,但在其他情况下将其作为局外人来查看方法签名似乎是有意义的。取一粒盐;我很少使用约束。
    【解决方案2】:

    对于任何感兴趣的人,目前,我使用通用类层次结构解决了最初的问题(流畅的事件调用 API)。这基本上是 Hightechrider 对类固醇的回答。

    public abstract class EventInvocatorParametersBase
        <TEventInvocatorParameters, TEventArgs>
        where TEventArgs : EventArgs
        where TEventInvocatorParameters :
            EventInvocatorParametersBase<TEventInvocatorParameters, TEventArgs>
    
    {
        protected EventInvocatorParametersBase(
            EventHandler<TEventArgs> eventHandler,
            Func<Exception, bool> exceptionHandler,
            Func<TEventArgs, bool> breakCondition)
        {
            EventHandler = eventHandler;
            ExceptionHandler = exceptionHandler;
            BreakCondition = breakCondition;
        }
    
        protected EventInvocatorParametersBase(
            EventHandler<TEventArgs> eventHandler)
            : this(eventHandler, e => false, e => false)
        {
        }
    
        public Func<TEventArgs, bool> BreakCondition { get; set; }
        public EventHandler<TEventArgs> EventHandler { get; set; }
        public Func<Exception, bool> ExceptionHandler { get; set; }
    
        public TEventInvocatorParameters Until(
            Func<TEventArgs, bool> breakCondition)
        {
            BreakCondition = breakCondition;
            return (TEventInvocatorParameters)this;
        }
    
        public TEventInvocatorParameters WithExceptionHandler(
            Func<Exception, bool> exceptionHandler)
        {
            ExceptionHandler = exceptionHandler;
            return (TEventInvocatorParameters)this;
        }
    
        public ConfiguredEventInvocatorParameters<TEventArgs> With(
            object sender, 
            TEventArgs eventArgs)
        {
            return new ConfiguredEventInvocatorParameters<TEventArgs>(
                EventHandler, ExceptionHandler, BreakCondition,
                sender, eventArgs);
        }
    }
    
    public class EventInvocatorParameters<T> :
        EventInvocatorParametersBase<EventInvocatorParameters<T>, T>
        where T : EventArgs
    {
        public EventInvocatorParameters(EventHandler<T> eventHandler)
            : base(eventHandler)
        {
        }
    }
    
    public class ConfiguredEventInvocatorParameters<T> :
        EventInvocatorParametersBase<ConfiguredEventInvocatorParameters<T>, T>
        where T : EventArgs
    {
        public ConfiguredEventInvocatorParameters(
            EventHandler<T> eventHandler,
            Func<Exception, bool> exceptionHandler,
            Func<T, bool> breakCondition, object sender,
            T eventArgs)
            : base(eventHandler, exceptionHandler, breakCondition)
        {
            EventArgs = eventArgs;
            Sender = sender;
        }
    
        public ConfiguredEventInvocatorParameters(EventHandler<T> eventHandler,
                                                  object sender,
                                                  T eventArgs)
            : this(eventHandler, e => false, e => false, sender, eventArgs)
        {
        }
    
        public T EventArgs { get; private set; }
        public object Sender { get; private set; }
    }
    
    public static class EventExtensions
    {
        public static EventInvocatorParameters<TEventArgs> Until<TEventArgs>(
            this EventHandler<TEventArgs> eventHandler,
            Func<TEventArgs, bool> breakCondition)
            where TEventArgs : EventArgs
        {
            return new EventInvocatorParameters<TEventArgs>(eventHandler).
                Until(breakCondition);
        }
    
        public static EventInvocatorParameters<TEventArgs> 
            WithExceptionHandler<TEventArgs>(
                this EventHandler<TEventArgs> eventHandler,
                Func<Exception, bool> exceptionHandler)
            where TEventArgs : EventArgs
        {
            return
                new EventInvocatorParameters<TEventArgs>(eventHandler).
                    WithExceptionHandler(exceptionHandler);
        }
    
        public static ConfiguredEventInvocatorParameters<TEventArgs>
            With<TEventArgs>(
                this EventHandler<TEventArgs> eventHandler, object sender,
                TEventArgs eventArgs)
            where TEventArgs : EventArgs
        {
            return new ConfiguredEventInvocatorParameters<TEventArgs>(
                eventHandler, sender, eventArgs);
        }
    }
    

    这使您可以编写如下代码:

    Fire.Event(EventName.WithExceptionHandler(e => false)
                        .Until(e => false).With(this, EventArgs.Empty));
    Fire.Event(EventName.With(this, EventArgs.Empty));
    Fire.Event(EventName.WithExceptionHandler(e => false)
                        .With(this, EventArgs.Empty).Until(e => false));
    Fire.Event(EventName.With(this, EventArgs.Empty)
                        .WithExceptionHandler(e => false).Until(e => false));
    

    但它不允许你写这个,因为没有提供所有必要的信息(eventArgs 和发件人):

    Fire.Event(EventName.Until(e => false));
    Fire.Event(EventName);
    

    【讨论】:

      【解决方案3】:

      您是否有某些原因需要使用扩展方法?如果将Until 放在EventInvocatorParameters&lt;T&gt; 类上,则可以避免上述两个问题:

      public class EventInvocatorParameters<T>
          where T : EventArgs
      {
          public Func<T, bool> BreakCondition { get; set; }
          // Other properties used below omitted for brevity.
      
          public EventInvocatorParameters<T> Until (Func<T, bool> breakCond)
          {
              this.BreakCondition = breakCond;
              return this;
          }
      }
      

      【讨论】:

      • 是的,这是有原因的。在题为“更广泛的图片(回答问题不是必需的!)”的问题的较长部分中对此进行了详细说明。也许这毕竟是必要的。简短版本:Until 可能会在 EventInvocatorParameters&lt;T&gt; 的派生类上调用,如果是这种情况,Until 的返回类型需要是该派生类型。
      【解决方案4】:

      我知道有点逃避,但您是否考虑过使用 Rx 而不是重新发明您似乎想要做的事情?

      【讨论】:

      • 不,我没有。谷歌没有为“流畅的事件调用 API”打开它。感谢您的指点,我会研究一下。
      • 您能否给我一个简短的例子,说明您将如何使用 Rx 来实现我想要的?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-11-15
      • 1970-01-01
      • 1970-01-01
      • 2021-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多