Generic type inferrence and anonymous methods

H

Harold Howe

I am running into a situation where the compiler complains that it
cannot infer the type parameters of a generic method when one of the
function arguments is an anonymous method.

Here is a complete code example:

//-----------------------------
using System;
using System.Collections.Generic;

interface ForwardIterator<T> {}
class IterImpl<T> : ForwardIterator<T> {}

static class Algorithm
{
// method that takes a delegate as its last argument
public static void ReplaceIf<T>(ForwardIterator<T> begin,
ForwardIterator<T> end,
Predicate<T> func)
{}

// another method that takes a delegate
public static I RemoveIf<I,T>(I begin, I end, Predicate<T> func)
where I: class, ForwardIterator<T>
{
return null;
}
}

class Program
{
static void Main(string[] args)
{
IterImpl<int> begin = new IterImpl<int>();
IterImpl<int> end = new IterImpl<int>();

// This compiles fine
Algorithm.ReplaceIf(begin, end, delegate(int x){return true;});

// This also compiles
Predicate<int> pred = delegate(int x){return true;};
Algorithm.RemoveIf(begin, end, pred);

// This errors out
Algorithm.RemoveIf(begin, end, delegate(int x){return true;});
}
}
//-----------------------------

The error is
main.cs(38,9): error CS0411: The type arguments for method
'Algorithm.RemoveIf<I,T>(I, I, System.Predicate<T>)' cannot be
inferred from the usage. Try specifying the type arguments explicitly.

I can get it to compile by passing template arguments, but that is
hideous. Why can't the compiler infer the second usage when it can infer
the first? Am I missing something simple?

H^2
 
R

Richard Blewett [DevelopMentor]

Harold Howe said:
I am running into a situation where the compiler complains that it cannot
infer the type parameters of a generic method when one of the function
arguments is an anonymous method.

look like a compiler bug to me seeing as the example that asigns the anon
delegate works. I can;t see any ambiguity there.

Regards

Richard Blewett - DevelopMentor
http://www.dotnetconsult.co.uk/weblog
http://www.dotnetconsult.co.uk
 
B

Barry Kelly

Harold Howe said:
I am running into a situation where the compiler complains that it
cannot infer the type parameters of a generic method when one of the
function arguments is an anonymous method.

Type inference for C# is not nearly as comprehensive as C++. You should
read section 25.6.4 of the C# standard ECMA-334 (3rd ed) before deciding
if it's a bug.

Part of the algorithm for type inference, described in 25.6.4:

---8<---
Nothing is inferred from the argument (but type inference succeeds) if
any of the following are true:
o P does not involve any method type parameters.
o The argument has the null type (§11.2.7).
o The argument is an anonymous method.
o The argument is a method group.
--->8---

In your case, the argument is an anonymous method, so nothing is
inferred from the argument. Since there aren't any more arguments to
infer the type parameter from, and one or more type parameters didn't
have a type argument inferred for it, inference overall fails. So as far
as I can make out, the behaviour you're seeing matches the standard.

Not actually exactly related to your problem, but you may find it
useful: beware the fact that anonymous delegates and method groups need
to go through an implicit conversion to a delegate type before they can
be considered for type inference. This implicit conversion requires that
the target delegate type be known before the anonymous delegate or
method group can be converted. For example:

---8<---
using System;

class App
{
static void F<T>(Action<T> a)
{
}

static void TakesT<T>(T x)
{
}

static void Main()
{
F<int>(TakesT); // Succeeds - can infer delegate type
// and thus T for argument to TakesT<>.

F(TakesT<int>); // Fails - can't infer delegate type for
// conversion so can't infer T for F<>.
}
}
--->8---

HTH,

-- Barry
 
H

Harold Howe

Thanks for the suggestions.

I have ran into a similar inferrence problem without involving
delegates. This is a simpler case to consider, and would have made a
better post than my original. I may repost a separate question. Here is
the code:

//---------------------------------
using System;
using System.Collections.Generic;

interface ForwardIterator<T> {}
class IterImpl<T> : ForwardIterator<T> {}

static class Algorithm
{
public static I MinElement<I,T>(I begin, I end)
where I: ForwardIterator<T>
where T: IComparable<T>
{
// real code snipped
return default(I);
}
}

class Program
{
static void Main(string[] args)
{
IterImpl<int> begin = new IterImpl<int>();
IterImpl<int> end = new IterImpl<int>();

IterImpl<int> min = Algorithm.MinElement(begin, end);
}
}
//-------------------------------------

I get the same compiler error:

main.cs(31,25): error CS0411: The type arguments for method
'Algorithm.MinElement<I,T>(I, I)' cannot be inferred from the usage.
Try specifying the type arguments explicitly.

Once again, passing type arguments is ugly, and I would like to avoid
it. I can work around this by not parameterizing on the iterator type.
ie if I do this:

public static ForwardIterator<T> MinElement<T>(ForwardIterator<T> begin,
ForwardIterator<T> end)
where T: IComparable<T>
....

However, this results in a small loss of type information. If I pass in
a strongly typed IterImpl<int>, I get back a weaker
ForwardIterator<int>. The original form returns the stronger type, but I
can't get the inference to work.

Am I out of luck on this? I will probably live with the loss of type
information, rather than force callers to pass type arguments.

H^2
 
B

Barry Kelly

Harold Howe said:
public static I MinElement<I,T>(I begin, I end)
where I: ForwardIterator<T>
where T: IComparable<T>

This is trying to infer the type argument from the type constraints -
can't be done (back to section 25.6.4 of ECMA-334 3rd ed):

---8<---
Method type parameter constraints, if any, are ignored for the purpose
of type inference.
--->8---
Am I out of luck on this? I will probably live with the loss of type
information, rather than force callers to pass type arguments.

If you are willing to limit the number of classes, you can keep some
type inference and keep a stronger return type by implementing the
method multiple times. From your example:

---8<---
using System;
using System.Collections.Generic;

interface ForwardIterator<T> {}
class IterImpl<T> : ForwardIterator<T> {}

static class Algorithm
{
public static ForwardIterator<T> MinElement<T>(
ForwardIterator<T> begin, ForwardIterator<T> end)
{
return null;
}
public static IterImpl<T> MinElement<T>(IterImpl<T> begin,
IterImpl<T> end)
{
return null;
}
}

class App
{
static void Main(string[] args)
{
IterImpl<int> begin = new IterImpl<int>();
IterImpl<int> end = new IterImpl<int>();

IterImpl<int> min = Algorithm.MinElement(begin, end);
}
}
--->8---

-- Barry
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top