Pass arbitrary actions to the same method - c#

Why is the following call ambiguous:
public class Foo
{
public void Bar<T> (Action<T> simple);
public void Bar<T1, T2> (Action<T1, T2> complex);
}
...
public class Test
{
public static void MyComplex (string a, string b) { ... }
}
...
foo.Bar(Test.MyComplex);
Shouldn't it be clear to the compiler to call the Bar<T1,T2>() method?

If you remove this method public void Bar<T> (Action<T> simple);, your code just will not compile, because you get this exception:
The type arguments for method 'Foo.Bar(System.Action)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
Unfortunately the compiler can not get types from this method, and you should write this code to call method:
new Foo().Bar(new Action<string, string>(Test.MyComplex));

The compiler is trying to infer the generic parameter types for Bar but to do that it needs to know all the argument types. The argument you have (Test.MyComplex) is actually method group, not a delegate so the compiler is also required to insert a conversion to a compatible delegate type. However it can't because it does not know the what delegate types to use because type inference on the method it needs to be compatible with has not completed yet. There's a chicken-and-egg problem and the compiler gives up saying the call is ambiguous. Eric Lippert points out in the comments to a very similar question that in simple cases like this, it could be worked out but at the expense of complicating the overload resolution rules.
Unfortunately you are required to do something that gives the compiler more information:
foo.Bar<string, string>(Test.MyComplex);
or
Action<string, string> action = Test.MyComplex;
foo.Bar(action);

Related

Syntax shortcut for calling generic methods

I just found out that in C# the following will compile
public void Exec<T>(T t) => Console.WriteLine(t.ToString());
and you can call it like
Exec(1);
Exec(new SomeClass());
What I find interesting is that you can omit the parameter type in the brackets and still get type safety (if you added constraints to the T). Is this a new feature? This seems nifty
This is called generic type inference (see here). The compiler can see you are passing in an int or a SomeClass so it infers the generic type. Take this example:
public void DoStuff<T, U>(T param1, U, param2) { ... }
You can call it like this:
DoStuff(1, "hello");
In fact, you've probably already used this feature without even realising it. All of the Linq methods are generically typed, so any time you've used Where, Count or Select for example, you could have specified the types. Now can you imagine how ugly that code would look? For example:
var list = new List<string>();
var filteredList1 = list.Where<string>(s => s == "hello");
var filteredList2 = list.Where(s => s == "hello");
However, how about this:
public TOutput DoStuff<TInput, TOutput>(TInput param1) { ... }
If you call it without specifying the types, you are only allowing the compiler to infer the first generic type so you will get a compiler error:
CS0411 The type arguments for method 'DoStuff(TInput)' cannot be inferred from the usage. Try specifying the type arguments explicitly.

Value of Generic Type when not Explicitly Passed?

In reviewing some code on github, I have come across this pattern:
using System.Linq.Expressions;
public T SomeGenericMethod<TValue>(Expression<Func<T, TValue>> myExpr){
// ...
}
This is the only relevant part of the code. The TValue never actually used in the method body, it is only present for use in the Func<,> type.
It's use then looks like this:
myObj.SomeGenericMethod(x => x.SomeProperty)
Note that no generic is passed on the call to SomeGenericMethod. I would have expected the compiler to require something like:
myObj.SomeGenericMethod<SomeTValue>(x => x.SomeProperty)
But it doesn't.
So my question is, what is TValue when nothing is explicitly passed as the type to the generic method invocation?
In this case, it is typeof(SomeProperty). The C# compiler will auto-discover it. From https://msdn.microsoft.com/en-us/library/twcad0zb.aspx
You can also omit the type argument and the compiler will infer it.
The compiler can infer the type parameters based on the method arguments you pass in; it cannot infer the type parameters only from a constraint or return value. Therefore type inference does not work with methods that have no parameters. Type inference occurs at compile time before the compiler tries to resolve overloaded method signatures. The compiler applies type inference logic to all generic methods that share the same name. In the overload resolution step, the compiler includes only those generic methods on which type inference succeeded.
Note that you can think that that usage is strange, but when you use LINQ, you do it normally. With Line you write:
var coll = new[] { new { Foo = 1, Bar = 2 } };
var enu = coll.Select(x => x.Foo).ToList();
You don't explicitly say anywhere the type of Foo in the Select. The compiler deducts it is an int.
The signature of the Select is:
public static IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
So the type of Foo is the TResult
Without this type inference, the anonymous objects would be nearly useless, because you couldn't:
class MyClass
{
public int Foo { get; set; }
public int Bar { get; set; }
}
var coll = new[] { new MyClass { Foo = 1, Bar = 2 } };
var enu = coll.Select<MyClass, ???>(x => new { Bar = x.Foo }).ToList();
What would you put in the ???? By definition of anonymous objects, you can't explicitly name them :-)
You can get away with not explicitly passing generics when the compiler can perform generic inferencing, wherein it will use the type of the arguments passed to determine the generic version to call upon. Note that inference happens at compile time, and thus can have some strange side effects with polymorphism.
In order to support the T value, this method would need to be within a class which has that T as a parameter. Because the class itself is compiled once per argument, any constituent members (including Subclasses!) can use that generic parameter, and this includes using it for the parameter of an argument, or the generic parameter of a further generic call.

Overload resolution issue for generic method with constraints

A code sample:
interface IFoo { }
class FooImpl : IFoo { }
static void Bar<T>(IEnumerable<T> value)
where T : IFoo
{
}
static void Bar<T>(T source)
where T : IFoo
{
}
Can anybody explain, why this method call:
var value = new FooImpl[0];
Bar(value);
targets Bar<T>(T source) (and, hence, doesn't compile)?
Does compiler take into account type parameter constraints at all, when resolving overloads?
UPD.
To avoid confusion with arrays. This happens with any implementation of IEnumerable<T>, e.g.:
var value = new List<FooImpl>();
UPD 2.
#ken2k mentioned covariance.
But let's forget about FooImpl. This:
var value = new List<IFoo>();
Bar(value);
produces the same error.
I'm sure, that implicit conversion between List<IFoo> and IEnumerable<IFoo> exists, since I can easily write something like this:
static void SomeMethod(IEnumerable<IFoo> sequence) {}
and pass value into it:
SomeMethod(value);
Does compiler take into account type parameter constraints at all, when resolving overloads?
No, because generic constraints are not part of the function signature. You can verify this by adding a Bar overload that is identical except for the generic constraints:
interface IBar { }
static void Bar<T>(IEnumerable<T> value)
where T : IFoo
{
}
static void Bar<T>(T source)
where T : IBar
{
// fails to compile : Type ____ already defines a member called 'Bar' with the same parameter types
}
The reason your code doesn't compile is because the compiler chooses the "best" match based on the method signature, then tries to apply the generic constraints.
One possible reason why it doesn't is because this call would be ambiguous:
{suppose List<T> had an Add<T>(IEnumerable<T> source) method}
List<object> junk = new List<object>();
junk.Add(1); // OK
junk.Add("xyzzy") // OK
junk.Add(new [] {1, 2, 3, 4}); //ambiguous - do you intend to add the _array_ or the _contents_ of the array?
The obvious fix is to use a different name for the Bar method that takes a collection (as is done in the BCL with Add and AddRange.
EDIT: Ok, the reason why Bar<T>(T source) is selected over Bar<T>(IEnumerable<T> source) when passing an List is because of the "7.5.3.2 Better function member" section of the C# language reference. What it says is that when an overload resolution must occur, the arguments types are matched to the parameters types of the applicable function members (section 7.5.3.1) and the better function member is selected by the following set of rules:
• for each argument, the implicit conversion from EX to QX is not better than the implicit conversion from EX to PX, and
• for at least one argument, the conversion from EX to PX is better than the conversion from EX to QX.
(PX being the parameter types of the first method, QX of the second one)
This rule is applied "after expansion and type argument substitution". Since type argument substitution will swap the Bar(T source) to Bar>(IList source), this method arguments will be a better match than the Bar(IEnumerable source) which needs a conversion.
I couldn't find a online version of the language reference, but you can read it here
EDIT: misunderstood the question initially, working on finding the correct answer in the c# language spec. Basically IIRC the method is selected by considering the most appropriate type, and if you don't cast your parameter to IEnumerable<> exactly, then the Bar<T>(T source) will match the parameter type exactly, just like in this sample:
public interface ITest { }
public class Test : ITest { }
private static void Main(string[] args)
{
test(new Test() ); // outputs "anything" because Test is matched to any type T before ITest
Console.ReadLine();
}
public static void test<T>(T anything)
{
Console.WriteLine("anything");
}
public static void test(ITest it)
{
Console.WriteLine("it");
}
Will link to it when found
Because the cast between an array and an enumerable must be explicit: this compiles
var value = new FooImpl[0].AsEnumerable();
Bar(value);
and so does this:
var value = new FooImpl[0] as IEnumerable<IFoo>;
Bar(value);
From the doc:
Starting with the .NET Framework 2.0, the Array class implements the
System.Collections.Generic.IList,
System.Collections.Generic.ICollection, and
System.Collections.Generic.IEnumerable generic interfaces. The
implementations are provided to arrays at run time, and as a result,
the generic interfaces do not appear in the declaration syntax for the
Array class.
So your compiler doesn't know that the array matches the signature for Bar, and you have to explicitly cast it
As of c# 7.3, generic constraints are now considered part of the method signature for the purpose of overload resolution. From What's new in C# 7.0 through C# 7.3: Improved overload candidates:
When a method group contains some generic methods whose type arguments do not satisfy their constraints, these members are removed from the candidate set.
Thus in c# 7.3 / .Net Core 2.x and later, the code shown in the question compiles and runs successfully, and Bar<T>(IEnumerable<T> value) is called as desired.
A demo .Net 5 fiddle here now compiles successfully
A demo .NET Framework 4.7.3 fiddle here still fails to compile with the error:
The type 'FooImpl[]' cannot be used as type parameter 'T' in the generic type or method 'TestClass.Bar<T>(T)'. There is no implicit reference conversion from 'FooImpl[]' to 'IFoo'.
That's a covariance issue. List<T> is not covariant, so there is no implicit conversion between List<FooImpl> and List<IFoo>.
On the other hand, starting from C# 4, IEnumerable<T> now supports covariance, so this works:
var value = Enumerable.Empty<FooImpl>();
Bar(value);
var value = new List<FooImpl>().AsEnumerable();
Bar(value);
var value = new List<FooImpl>();
Bar((IEnumerable<IFoo>)value);

Generic Method Resolution

Consider the following code:
public class Tests
{
public void Test()
{
Assert.AreEqual("Int", DoSomething(1));
}
public static string DoSomething<T>(T value)
{
return "Generic";
}
public static string DoSomething(int value)
{
return "Int";
}
}
As expected, the non-generic DoSomething method will be invoked. Now consider the following modification:
public class Tests
{
public void Test()
{
Assert.AreEqual("Int", DoSomething(1));
}
public static string DoSomething<T>(T value)
{
return "Generic";
}
public static string DoSomething<T>(int value)
{
return "Int";
}
}
The only thing I've changed is adding the T type parameter to the second overload, thus making it generic. Note that the type parameter is not used.
That modification causes the first DoSomething method to be called. Why? The compiler has all the information it needs in order to choose the second method.
Can you please explain why, or even better, point me to the section of the C# specification that explains this behavior?
In your call, you're not specifying a type argument - so the compiler would have to infer the type of T. It can't do that for your second method, because the type parameter is never mentioned in the declared parameters. Therefore, that overload is not applicable, and is ignored.
If you specify a type argument to the call, e.g. any of
DoSomething<int>(1)
DoSomething<object>(1)
DoSomething<string>(1)
... then in all cases the second overload will be called.
From section 7.6.5.1 of the C# 5 spec, (method invocations) when constructing the set of candidate methods:
If F is generic and M has no type argument list, F is a candidate when:
Type inference (§7.5.2) succeeds, inferring a list of type arguments for the call, and
Once the inferred type arguments are substituted for the corresponding method type parameters, all constructed types in the parameter list of F satisfy their constraints (§4.4.4), and the parameter list of F is applicable with respect to A (§7.5.3.1).
As type inference doesn't succeed, the second method isn't in the candidate set, so by the time we get to real overload resolution, the set just has a single method in (the first one).
the compiler can not determine the type of pattern in the call DoSomething(1), but if you specify [int] it will be selected another method.

Ambiguous method call with Action<T> parameter overload

I encountered some unexpected compiler behaviour when calling overloaded method with different Action<T> variations.
Let's say I have this class Test and I'm creating its instance in the CallTest constructor.
public class Test
{
public Test(Action<long> arg)
{
}
public Test(Action<decimal> arg)
{
}
}
public class CallTest
{
public CallTest()
{
Test t = new Test(TestDecimal);
}
public void TestDecimal(decimal arg)
{
}
public void TestLong(long arg)
{
}
}
When calling the Test constructor with either TestDecimal or TestLong as a parameter I'm receiving the following error:
The call is ambiguous between the following methods or properties: 'Test(System.Action<long>)' and 'Test(System.Action<decimal>)'
My guess is there's some implicit conversion going on between long and decimal, but does anyone have any other idea what could have I done wrong? Is there any workaround?
When you pass TestDecimal or TestLong as a parameter you're in fact passing a method group (after all, there could be more than one TestDecimal method - it could have been overloaded). So in both cases implicit conversion occurs - from a method group to a particular delegate type. Both methods are thus applicable candidates (Section 7.4.2). From applicable candidates the overload resolution algorithm tries to find the best candidate. However, the rules of comparing conversions when matching parameter lists state, that if for both candidates implicit conversion occurs neither of them is better:
Section 7.4.2.3:
[...]
Otherwise, neither conversion is better.
That's why in your case there is an ambiguity.
The workaround is of course to first cast the parameter explicitly:
new Test(new Action<decimal>(TestDecimal))
This way for one case there will be no need for implicit conversion during overload resolution (as after the cast Action<T> type will match exactly), and the other would have to be converted (Action<long> to Action<decimal>), and the section mentioned above states that:
[...]
If S is T1, C1 is the better conversion.
If S is T2, C2 is the better conversion.
[...]
There is a workaround:
Test t = new Test(new Action<decimal>(TestDecimal));
This is due to an implicit casting between long and decimal.
Here's a table of implicit castings(for simple types) in C#(Picture Source):
Read more about type conversions here.

Categories