Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
114 views
in Technique[技术] by (71.8m points)

c# - 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.
  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();
Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...