简体   繁体   English

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

[英]No type inference with generic extension method

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 .此扩展方法显示在所有类型上,甚至string
  2. I can't write new EventInvocatorParameters<EventArgs>(EventABC).Until(e => false);我不能写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> :为了确保这种方法只能用完全配置的参数来调用,它仅接受一个ConfiguredEventInvocatorParameters<T>其中导出从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> .为了完成这项工作,存在名为With扩展方法,它们接受EventHandler<TEventArgsTEventInvocatorParameters并返回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. With now 之后的所有调用也需要返回类型ConfiguredEventInvocatorParameters<TEventArgs> ,否则有效调用的第二个示例(以Until结尾)将不起作用。
If you have any thoughts on the API in general, please let me know.如果您对 API 有任何想法,请告诉我。 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()创建一个像EventName.With(...).Until(...).Fire()这样的反向语法
  • Use the infamous Do method to start off things: Fire(EventName).With(...).Until(...).Do();使用臭名昭著的Do方法开始: Fire(EventName).With(...).Until(...).Do();

UPDATE from November 2020 : The original answer below was written in 2011; 2020 年 11 月更新:以下原始答案写于 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#;泛型方法类型推断、重载解析以及方法的“最终验证”如何完成的规则​​在最近的 C# 版本中发生了微小但显着的变化; this answer, and the link to an archived article on my original MSDN blog about it might no longer be accurate.这个答案,以及我原来的 MSDN 博客上关于它的存档文章的链接可能不再准确。 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.我希望在某个时候有时间重新阅读这篇文章,以澄清 (1) 今天的规则,(2) 它们是如何改变的,以及 (3) 在那些被删除的评论中讨论的想法如何影响这些决定,但这很多工作,我可能在一段时间内无法完成。 Remember, I have not been on the C# language design team since November 2012.请记住,自 2012 年 11 月以来,我一直没有加入 C# 语言设计团队。


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://docs.microsoft.com/en-gb/archive/blogs/ericlippert/constraints-are-not-part-of-the-signature https://docs.microsoft.com/en-gb/archive/blogs/ericlippert/constraints-are-not-part-of-the-signature

For anyone interested, for now, I solved the original problem (fluent event invocation API) with a generic class hierarchy.对于任何感兴趣的人,现在,我使用通用类层次结构解决了原始问题(流畅的事件调用 API)。 This is basically Hightechrider's answer on steroids.这基本上是 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);
    }
}

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:但它不允许你写这个,因为没有提供所有必要的信息(eventArgs 和 sender):

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

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:如果你把Until EventInvocatorParameters<T>类,你可以避免提到的两个问题:

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM