No Type Inference with Generic Extension Method

No type inference with generic extension method

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

Generic extension method type inference

You can get most of what you are looking for by using a fluent interface:

void Main()
{
var b = new ushort[] { 1, 256 };

// We now use a fluent interface to do the conversion - T1 is
// now inferred.
var us = b.Convert().To<byte>();

us.Dump();
}

public static class Extensions
{
public static IConverter Convert<T1>(this T1[] buffer)
{
return new Converter<T1>(buffer);
}
}

public interface IConverter
{
T2[] To<T2>();
}

public class Converter<T1> : IConverter
{
private readonly T1[] _buffer;
public Converter(T1[] buffer)
{
_buffer = buffer ?? throw new ArgumentNullException();
}

public T2[] To<T2>()
{
var bufferNumBytes = _buffer.Length * Marshal.SizeOf(default(T1));
var targetElementNumBytes = Marshal.SizeOf(default(T2));
if (bufferNumBytes % targetElementNumBytes != 0)
{
throw new ArgumentException($"Array must have multiple of {targetElementNumBytes} bytes, has {bufferNumBytes} bytes instead", nameof(_buffer));
}
var res = new T2[bufferNumBytes / targetElementNumBytes];
Buffer.BlockCopy(_buffer, 0, res, 0, bufferNumBytes);
return res;
}
}

The downsides are you are allocating extra memory for the Converter class, and you end up calling two methods - but discoverability should be basically as good as your original example, which is what I think is really motivating your question.

Extension Method With Generic Parameter To Generic Class

Why?

Type argument inference is all or nothing: you've got to either specify all the type arguments, or none of them, on each method call.

The M type parameter can't be inferred, because it doesn't occur anywhere in the parameter list.

A few options:

  • Chain two methods together, so that the first can use type inference and the second will let you specify the type argument for M:

    Bar<double> bar = foo.CreateConverter()   // Implicitly CreateConverter<int>
    .ConvertTo<double>() // Specify that you want a Bar<double>

    This would require a new intervening type, e.g. Converter<T> to be returned by CreateConverter. That would have a regular instance method of ConvertTo<M>.

  • Add a parameter of type M to the Convert method:

    public static Bar<M> Convert<T, M>(this Foo<T> list, M ignored)

    ... you can then call the method as:

    Bar<double> bar = foo.Convert(default(double));

    That's somewhat smelly, admittedly.

  • Don't use an extension method, but a regular static generic method in a generic type - where both the type and the method have a single type parameter:

    public static class Converters<M>
    {
    public static Bar<M> Create<T>(Foo<T> foo) { ... }
    }

    then invoke it as:

    Bar<double> bar = Converters<double>.Create(foo);

    The type argument for the method can be inferred from foo, and you're specifying the type argument for the type explicitly.

Type inference problem when writing a generic extension method with more than one type

The problem is that the type inference for T1 doesn't work

Actually, the problem isn't T1 - it is T2; return types are not used in this inference. So you can't do that. One option might be a fluent API, for example:

return articles.Map(_mappingEngine).To<SomeT2>();

with something like:

public static MapProjection<T1> Map<T1>(this IEnumerable<T1> list, IMappingEngine engine)
{
return new MapProjection<T1>(list, engine);
}
public class MapProjection<T1>
{
private readonly IEnumerable<T1> list;
private readonly IMappingEngine engine;
internal MapProjection( IEnumerable<T1> list, IMappingEngine engine)
{this.list = list; this.engine = engine;}

public IEnumerable<T2> To<T2>()
{
return list.Select(engine.Map<T1, T2>());
}
}

assuming that the interface is something like:

public interface IMappingEngine {
Func<T1, T2> Map<T1, T2>();
}

Trouble with type inference in writing generic extension method

You seem to be defining your extension method with three generic types when you only need two. "TValue" and "TType" mean the same thing, don't they? Try this:

public static TValue GetValueOrDefault<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary, TKey key)
{
TValue value;
// attempt to get the value of the key from the dictionary
dictionary.TryGetValue(key, out value);
return value;
}

Generic extension method : Type argument cannot be inferred from the usage

Method type inference does not make inferences from arguments to constraints. It makes inferences from arguments to formal parameters and then checks whether the inferences made from the arguments to the formals satisfy the constraints.

In your case there is not enough data from the arguments to deduce what the type parameters are without first looking at the constraints, which we're not going to do until we check the inferences against the constraints. Sorry about that, but that's how the type inference algorithm is specified.

I've been asked questions about this many times and the consensus seems to be that I am morally wrong for maintaining the position that inference should infer from arguments to formal parameters alone. For about a dozen people telling me I'm wrongheaded in this regard, see the comments to my analysis of this closely related issue:

http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx

I maintain my position.

Extension methods and type inference

One possibility would be to create a base class for each level and an empty handler class deriving from it:

Base classes:

public abstract class ZooKeeperBase<TZooKeeper, TAnimal>
where TZooKeeper : ZooKeeperBase<TZooKeeper, TAnimal>
where TAnimal : Animal
{
private string name;
private List<TAnimal> animalsFed = new List<TAnimal>();
private TAnimal favoriteAnimal;

public TZooKeeper Name(string name)
{
this.name = name;
return (TZooKeeper)this;
}

public TZooKeeper FeedAnimal(TAnimal animal)
{
animalsFed.Add(animal);
return (TZooKeeper)this;
}

public TZooKeeper Favorite(Func<TAnimal, bool> animalSelector)
{
favoriteAnimal = animalsFed.FirstOrDefault(animalSelector);
return (TZooKeeper)this;
}
}

public abstract class ExperiencedZooKeeperBase<TZooKeeper, TAnimal>
: ZooKeeperBase<TZooKeeper, TAnimal>
where TZooKeeper : ExperiencedZooKeeperBase<TZooKeeper, TAnimal>
where TAnimal : Animal
{
private List<TAnimal> animalsCured = new List<TAnimal>();

public TZooKeeper CureAnimal(TAnimal animal)
{
animalsCured.Add(animal);
return (TZooKeeper)this;
}
}

Handler classes:

public class ZooKeeper<T> : ZooKeeperBase<ZooKeeper<T>, T>
where T : Animal
{
}

public class ExperiencedZooKeeper<T>
: ExperiencedZooKeeperBase<ExperiencedZooKeeper<T>, T>
where T : Animal
{
}

Usage would be just as you showed in your question.

Why generic extension method with constraint is not recognized as extension method?

Method type inference does not take constraints into account when making inferences.

This same question was asked yesterday. See my answer there for more details.

No type inference with generic extension method



Related Topics



Leave a reply



Submit