Visual Studio 2015 extension method call as method group
Asked Answered
M

3

11

I have an extension method like

public static void RemoveDetail<TMaster, TChild>(this TMaster master, TChild child)
        where TMaster : class, IMaster<TChild>
        where TChild : class, IDetail<TMaster>;

And I have two classes

public class Principal : IMaster<Permission>
{
        public virtual IEnumerable<Permission> Permissions { get; }
}

and

public class Permission : IDetail<Principal>

I call RemoveDetail from an Action accepted by method public static void Foreach<T>(this IEnumerable<T> source, Action<T> action);:

aPrincipal.Permissions.Foreach(x => aPrincipal.RemoveDetail(x));

ReSharper suggest me to replace this call with method group like

aPrincipal.Permissions.Foreach(aPrincipal.RemoveDetail);

This worked fine in VS2013 and previous but it fails on compilation in VS2015 with

'Principal' does not contain a definition for 'RemoveDetail' and no extension method 'RemoveDetail' accepting a first argument of type 'Principal' could be found (are you missing a using directive or an assembly reference?)

Anyone has a suggestion? Do I have to update all of the usages and make ReSharper to avoid this replacement?

Mahoney answered 2/9, 2015 at 10:14 Comment(7)
This is supposed to work. The C# Language Specification says this shall work.Rakia
it compiles in my VS 2015Fulks
Stupid question, but is your project targeting the correct version of the framework?Agbogla
Please post a complete example. I can't reproduce this either using List<T>.ForEach. What is the type of z? Are you sure you aren't missing a reference or a static keyword somewhere?Bloodmobile
updated to more specific usageMahoney
@Agbogla I didn't update target Framework. It was .Net 4 in VS13 and it is still 4Mahoney
Upvoted because this question possibly found a bug in VS2015 compiler, I just wish it was asked in a better way...Muckrake
G
7

I was able to reproduce your problem

public interface IDetail<T>
{

}

public interface IMaster<T>
{

}

public class MyClass
{
    public void method()
    {
        Principal aPrincipal = new Principal();
        aPrincipal.Permissions.Foreach(x => aPrincipal.RemoveDetail(x)); // No suggestion from resharper
    }
}

public class Permission : IDetail<Principal>
{

}

public class Principal : IMaster<Permission>
{
    public virtual IEnumerable<Permission> Permissions { get; }
}

public static class Class
{
    public static void RemoveDetail<TMaster, TChild>(this TMaster master, TChild child)
        where TMaster : class, IMaster<TChild>
        where TChild : class, IDetail<TMaster>
    {

    }

    public static void Foreach<T>(this IEnumerable<T> source, Action<T> action)
    {

    }
}

The resharper does not suggest anything. so probably you have to update your Resharper to newer version. probably was a bug which is fixed now.

If this is not like the way you do. then you have to put more information.

Also Compiler gives same error when i try to convert it to method group. So this question may remain: Why Compiler cant do this?

Edit:

To fix this problem you have to call the RemoveDetail method from inside Principal. so make your Principal class like this.

public class Principal : IMaster<Permission>
{
    public virtual IEnumerable<Permission> Permissions { get; }

    public void RemoveDetail(Permission p)
    {
        Class.RemoveDetail(this, p);
    }
}

I think there is kind of ambiguous inside compiler (Possibly bug) that cant recognize RemoveDetail method. how ever compiler tries to find it inside Principal. so you can fix it with creating RemoveDetail inside Principal and call a static RemoveDetail from there.

Edit 2:

The problem is generic type constraint.

where TMaster : class, IMaster<TChild>

This makes compiler to look at class which implements IMaster<TChild> and thats Principal. if you remove this where clause it will solve the problem. otherwise the compiler expects that it should be Principal.

If you use lambda you remove this ambiguity.

aPrincipal.RemoveDetail // compiler expects property/field/method in Principal
                        // but compiler forgets method group and static generic method!
x => aPrincipal.RemoveDetail(x) // this is no more like property or field
                                //but a method that is considered static generic method!

So thats a C#6 bug

Gondolier answered 2/9, 2015 at 11:26 Comment(18)
If I replace x => aPrincipal.RemoveDetail(x) in above code with just aPrincipal.RemoveDetail it compiles under VS2013, C# 5.0, targeting .NET 4.5.2. Can anyone confirm that this will not compile under the new C# compiler?Rakia
I tested in Visual studio 2015. C# 6.0, targeting .NET 4.6 and it does not work with aPrincipal.RemoveDetail @JeppeStigNielsenGondolier
I confirm the first form does compile, but the second form does not compile in VS2015 on 4.5.2. BTW if I comment out where TMaster : class, IMaster<TChild> from RemoveDetail method, it compiles fine. I guess they changed the way extensions are inferred for method group.Muckrake
There must be a bug in the new C# compiler!Rakia
In the C# Language Specification, the section (section no. 6.6 in my version) Method group conversions of the chapter Conversions makes it very clear that an extension method can be found from a method group in this way. So this is required to work.Rakia
I contacted Eric Lippert and asked him to have a loo at this thread.Radmilla
@OlivierJacot-Descombes Does Eric Lippert have anything to do with the C# compiler any more?Africanist
Note that public virtual IEnumerable<Permission> Permissions { get; } is an auto-property without a set accessor. This is allowed in C# 6.0 only. If you want to try the above example under C# 5.0 or earlier, try e.g. to say { get; set; } instead, to make that part compile.Rakia
@JeppeStigNielsen i have found the problem. its bug in C#6. see the new editGondolier
@M.kazem Akhgary thank you for the answer! That is exact what I have. Sorry for an odd way of asking, just my first time.Mahoney
@Jeppe Stig Nielsen this is not auto-property, I just extracted it from metadata. Actually the property has backing fieldMahoney
I do not have C# 6.0 here. Can you call the extension method in an ordinary way. I.e. can you say aPrincipal.RemoveDetail(new Permission()); in the bottom of the body of method method above?Rakia
@JeppeStigNielsen yes you can but it's not a method groupMahoney
@M.kazemAkhgary just updated to Resharper 9.2 and it's the same. Switched off convert lambda expression to method group in Code Inspection | Inspection Severity as there is no other wayMahoney
i have resharper 9.1.1. i will upgrade it and let you know the behavior. probably resharper got the bug again! i did not changed the options. and resharper suggests convert if i remove the where clause. @MahoneyGondolier
@Mahoney i dont know why but resharper still does not suggest anything for me.how ever i hope this bug is reported and they will fix it soon.Gondolier
I have entered the example in dotnetfiddle.net. C# 6.0 (roslyn 1.0.0-rc): dotnetfiddle.net/6VjBpk and C# 5.0: dotnetfiddle.net/245npq but both examples show the same behavior.Radmilla
@OlivierJacot-Descombes It does work on Mono v3.10.0 : tutorialspoint.com/compile_csharp_online.php .how ever this does not compile with only getter property. but both your tests are working with only getter property. probably its a bug inside update on both versions C#6 and C#5.Gondolier
R
1

I gave a feedback on this subject to Microsoft on September the 3rd 2015 here:
https://connect.microsoft.com/VisualStudio/feedback/details/1747835/visual-studio-2015-extension-method-call-as-method-group

Today I got this feedback from Neal [MSFT]:

This is fixed in VS2015 Update 1.
It is tracked here: https://github.com/dotnet/roslyn/issues/4970

Radmilla answered 12/11, 2015 at 20:41 Comment(0)
A
-2

I don't see how it worked in VS2013. RemoveDetail is a method of TMethod not your Action MyClass.

It should be something like

z.ForEach(x => master.RemoveDetail(x);
Addams answered 2/9, 2015 at 10:24 Comment(9)
Yes it does. The extension method essentially works for any object as there is no constraint to the generic type TMaster. The compiler succesfully resolves TMaster to MyClass.Distil
full signature is public static void RemoveDetail<TMaster, TChild>(this TMaster master, TChild child) where TMaster : class, IMaster<TChild> where TChild : class, IDetail<TMaster>; this here is an instance of master class, so it owrksMahoney
@Mahoney Does MyClass implement IMaster<TChild>?Distil
@Mahoney please post an complete example that does reproduce the problem. As others noted, the (incomplete) code you provided compiles just fine in 2015 once some typos are fixedBloodmobile
@InBetween: I am assuming that MyClass does NOT implement IMaster<TChild> and stand by my answer.Addams
@RichardSchneider The code compiles. If TMaster has no constraint then any object is a valid choice: public static Foo<T>(this T obj) will succesfully resolve to any T. Can you find a type for T that will not compile succesfully? The signature originally posted by the OP is essentially the same..Distil
@Distil But OP says TMaster has constraint class, IMaster<TChild>.Addams
@RichardSchneider That information was initially brought up by the OP in comments to your answer so how did you manage to know about this before answering? We got ourselves a time travel riddle here.Distil
@Distil It was just an assumption. I've seen this master/child pattern in may places.Addams

© 2022 - 2024 — McMap. All rights reserved.