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:
- This extension method shows on all types, even
string
. - 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();
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:
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);
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;
}
}
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?
来源:https://stackoverflow.com/questions/7171067/no-type-inference-with-generic-extension-method