通用扩展方法没有类型推断

发布于 2024-12-02 04:16:50 字数 3698 浏览 1 评论 0 原文

我有以下方法:

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(EventABC).Until(e => 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;
                }
            }
        }
    }
}

为了确保只能使用完全配置的参数调用此方法,它只接受派生自 EventInvocatorParametersConfiguredEventInvocatorParameters

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 的扩展方法,它接受EventHandlerTEventInvocatorParameters 并返回 ConfiguredEventInvocatorParametersWith 之后的所有调用现在还需要返回类型 ConfiguredEventInvocatorParameters,否则是有效调用的第二个示例(使用 Until最后)行不通。
如果您对 API 有任何总体想法,请告诉我。但是,我想避免以下三件事:

  • 如果参数尚未完全配置,则仅在运行时失败
  • 创建类似 EventName.With(...).Until(...).Fire() 的反向语法
  • 使用臭名昭著的 Do 方法来开始:Fire(EventName).With(...).Until(...).Do();

I have the following method:

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

And this class

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

Now, I have the following problems:

  1. This extension method shows on all types, even string.
  2. I can't write new EventInvocatorParameters<EventArgs>(EventABC).Until(e => false); It is telling me "The type arguments for method ... cannot be inferred from the usage."

Can't I use generic type parameters like this? How would you resolve this problem?
Important point: I need both of those generic parameters, because I need to return the same type this extension method was called on.


Broader picture (not necessary for answering the question!):
I am trying to create a fluent interface to invoking events. The base is this static class:

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

To make sure this method can only be called with fully configured parameters, it only accepts a ConfiguredEventInvocatorParameters<T> which derives from EventInvocatorParameters<T>:

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

}

The following would be valid calls:

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

The following would be invalid:

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

To make this work, there exist extension methods named With, that accept either a EventHandler<TEventArgs or a TEventInvocatorParameters and return a ConfiguredEventInvocatorParameters<TEventArgs>. All calls following the With now also need to return the type ConfiguredEventInvocatorParameters<TEventArgs>, otherwise the second example of a valid call (with the Until at the end) wouldn't work.
If you have any thoughts on the API in general, please let me know. However, I want to avoid the following three things:

  • Fail only at runtime if the parameters have not been configured fully
  • Creating an inverse syntax like EventName.With(...).Until(...).Fire()
  • Use the infamous Do method to start off things: Fire(EventName).With(...).Until(...).Do();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

若无相欠,怎会相见 2024-12-09 04:16:50

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


泛型方法类型推断故意从约束中进行任何推导。相反,从参数形式参数进行推导,然后根据约束检查推导的类型参数。

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

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

UPDATE from November 2020: The original answer below was written in 2011; the rules for generic method type inference, overload resolution, and how "final validation" of methods is done have had small but significant changes in recent versions of C#; this answer, and the link to an archived article on my original MSDN blog about it might no longer be accurate. Also, Microsoft deleted the comments on the original article for legal reasons; there was a huge amount of context and discussion in those comments. I hope to at some point have the time to revisit this article to clarify (1) the rules today, (2) how they have changed, and (3) how the ideas discussed in those deleted comments influenced those decisions, but that's a lot of work and I may not get to it for some time. Remember, I have not been on the C# language design team since November 2012.


Generic method type inference deliberately does not make any deductions from the constraints. Rather, deductions are made from the arguments and the formal parameters, and then the deduced type arguments are checked against the constraints.

For a detailed discussion of some of the design issues around constraints and method signatures, including several dozen people telling me that I'm wrong to think that the existing design is sensible, see my article on the subject:

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

流殇 2024-12-09 04:16:50

对于任何感兴趣的人,目前,我使用通用类层次结构解决了最初的问题(流畅的事件调用 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);

For anyone interested, for now, I solved the original problem (fluent event invocation API) with a generic class hierarchy. This is basically Hightechrider's answer on steroids.

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

This allows you to write code like this:

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

But it doesn't allow you to write this, because not all necessary info (eventArgs and sender) has been provided:

Fire.Event(EventName.Until(e => false));
Fire.Event(EventName);
独行侠 2024-12-09 04:16:50

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

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

Is there some reason you need to use an extension method? If you put Until on the EventInvocatorParameters<T> class you can avoid both of the problems mentioned:

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;
    }
}
〃温暖了心ぐ 2024-12-09 04:16:50

我知道有点逃避,但是您是否考虑过使用 Rx 来代替,而不是重新发明你似乎正在尝试做的事情?

Bit of a cop-out I know, but have you considered using Rx instead, rather than re-inventing what you appear to be trying to do?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文