10

Using VS2013, in the following example two different errors are given when attempting to pass a function to a worker's constructor, yet, lambda functions with the same prototype are ok.

What am I doing wrong, and how can I change the definition of the GetA function to allow it to be passed?

In order to avoid confusion with similar-sounding questions caused by misunderstanding how class inheritance works, I've intentionally avoided any inheritance in this example.

WorkerA can only accept a Func<A> in it's contructor. WorkerAorB is more flexible, and can accept either a Func<A> or a Func<B>. WorkerAandB is the most capable, and can accept a Func<A> and a Func<B> simultaneously (or either). It is the closest to my real code.

However, when code in the manager attempts to instantiate workers, WorkerA works as expected, but WorkerAorB gives the error:

error CS0121: The call is ambiguous between the following methods or properties: 'WorkerAorB.WorkerAorB(System.Func<A>)' and 'WorkerAorB.WorkerAorB(System.Func<B>)'

WorkerAandB gives

error CS0407: 'A ManagerA.GetA()' has the wrong return type

In each case, it seems that the compiler becomes unable to determine which overload to use when it is being passed a reference to a real function rather than a lambda or an existing Func<A> variable, and in the WorkerAandB case it is unambiguously selecting the WRONG overload, and giving an error about the return type of the passed function.

class A { }
class B { }

class WorkerA
{
  public WorkerA(Func<A> funcA) { }
}

class WorkerAorB
{
  public WorkerAorB(Func<A> funcA) { }
  public WorkerAorB(Func<B> funcB) { }
}

class WorkerAandB
{
  public WorkerAandB(Func<A> funcA, Func<B> funcB = null) { }
  public WorkerAandB(Func<B> funcB) { }
}
class ManagerA
{
  A GetA() { return new A(); }
  static A GetAstatic() { return new A(); }
  Func<A> GetAfunc = GetAstatic;

  ManagerA()
  {
    new WorkerA(() => new A()); // ok
    new WorkerA(GetA); // ok
    new WorkerA(GetAstatic); // ok

    new WorkerAorB(() => new A()); // ok
    new WorkerAorB(() => new B()); // ok
    new WorkerAorB(GetA); // error CS0121
    new WorkerAorB(GetAstatic); // error CS0121
    new WorkerAorB(() => GetA()); // ok
    new WorkerAorB(GetAfunc); // ok

    new WorkerAandB(() => new A()); // ok
    new WorkerAandB(GetA); // error CS0407
    new WorkerAandB(GetAstatic); // error CS0407
    new WorkerAandB(GetA, null); // ok
    new WorkerAandB(GetAstatic, null); // ok
    new WorkerAandB(GetAfunc); // ok
  }
}

// class ManagerB or ManagerAandB left as an exercise to the reader!

Can the GetA or GetAstatic functions be modified in some way to help the compiler recognise the correct overload to use, or are only lambdas and/or explicitly-declared delegates allowed in this context?

Update: Some information that I'd omitted from the example. In the real problem, classes A and B are in fact related.

class B : A { }

Also, on further reflection of the real problem, a call to

public WorkerAandB(Func<B> funcB) { }

like

    new WorkerAandB(GetB)

was in fact equivalent to

    new WorkerAandB(GetB, GetB)

So for the real problem, I've done the equivalent of deleting the second constructor in the example problem, as it turns out the overload was redundant.

In the meantime I've accepted the answer that actually gave a potential solution to the problem (albeit an obvious one that I'd omitted to mention in the original question), even though it wasn't what I eventually used.

Steve
  • 613
  • 5
  • 15

3 Answers3

2

The key part of Eric Lippert's answer here, as it applies to this question, appears to be that "overhead resolution does not consider return types." So if you rewrite your example by replacing every Func with an Action, the errors disappear, as now there are non-empty argument lists by which the ambiguity is resolved.

class A { }
class B { }

class WorkerA
{
    public WorkerA(Action<A> doA) { }
}

class WorkerAorB
{
    public WorkerAorB(Action<A> doA) { }
    public WorkerAorB(Action<B> doB) { }
}

class WorkerAandB
{
    public WorkerAandB(Action<A> doA, Action<B> doB = null) { }
    public WorkerAandB(Action<B> doB) { }
}
class ManagerA
{
    void DoA(A a) { }
    static void DoAstatic(A a) { }
    Action<A> DoAfunc = DoAstatic;

    ManagerA()
    {
        new WorkerA((A a) => { }); // ok
        new WorkerA(DoA); // ok
        new WorkerA(DoAstatic); // ok

        new WorkerAorB((A a) => { }); // ok
        new WorkerAorB((B b) => { }); // ok
        new WorkerAorB(DoA); // ok
        new WorkerAorB(DoAstatic); // ok
        new WorkerAorB(a => { }); // ok
        new WorkerAorB(DoAfunc); // ok

        new WorkerAandB(a => { }); // ok
        new WorkerAandB(DoA); // ok
        new WorkerAandB(DoAstatic); // ok
        new WorkerAandB(DoA, null); // ok
        new WorkerAandB(DoAstatic, null); // ok
        new WorkerAandB(DoAfunc); // ok
    }
}
Community
  • 1
  • 1
Joe Farrell
  • 3,502
  • 1
  • 15
  • 25
1

The answer would be this :

    public ManagerA()
    {
        new WorkerA(() => new A()); // ok
        new WorkerA(GetA); // ok
        new WorkerA(GetAstatic); // ok

        new WorkerAorB(() => new A()); // ok
        new WorkerAorB(() => new B()); // ok
        new WorkerAorB((Func<A>)GetA); // cast to avoid error CS0121
        new WorkerAorB((Func<A>)GetAstatic); // cast to avoid error CS0121
        new WorkerAorB(() => GetA()); // ok
        new WorkerAorB(GetAfunc); // ok

        new WorkerAandB(() => new A()); // ok
        new WorkerAandB((Func<A>)GetA); // cast to avoid error CS0407
        new WorkerAandB((Func<A>)GetAstatic); // cast to avoid error CS0407
        new WorkerAandB(GetA, null); // ok
        new WorkerAandB(GetAstatic, null); // ok
        new WorkerAandB(GetAfunc); // ok
    }

As for why it didn't work without the casts.... Seems that for the compiler GetA is not from the type Func<A> but only a method group

Irwene
  • 2,807
  • 23
  • 48
  • This seems the only answer that actually solved the problem... and I'd not mentioned the cast solution in the question... In fact I've used a different solution in the end, using properties of the real problem that were not present in the example. – Steve Apr 17 '15 at 20:31
0

The overall problem is that why would you write code like this? I see the hypothetical nature of the problem, but it shouldn't ever exist in the real world because we should write functions that actually read as expected:

public Person GetPersonById(func<int> personIdFunc)

there is no other way to write a function to Get a person by ID by passing in a function that returns an int.

Or when creating multiple constructors, using the correct object oriented approach solves a problem:

class Person
{
  public Person(Func<B> funcB) 
    :this(null, funcB)
  { }
  public Person(Func<A> funcA, Func<B> funcB) { }
}

But directly to your question..

Can the GetA or GetAstatic functions be modified in some way to help the compiler recognise the correct overload to use, or are only lambdas and/or explicitly-declared delegates allowed in this context?

As far as I am aware it cannot on the function definitions, but it can be done on it's use:

new WorkerAorB((Func<A>)GetA); // error CS0121
new WorkerAorB((Func<A>)GetAstatic); // error CS0121
new WorkerAandB((Func<A>)GetA); // error CS0407
new WorkerAandB((Func<A>)GetAstatic); // error CS0407
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
  • In the real code, only the equivalent to WorkerAandB exists, and the equivalent of the Manager (currently a unit test, as there's no real manager yet), only instantiates one worker. The other classes were used to illustrate the boundary between what works and what doesn't. Client code can provide the delegate that returns A, the delegate that returns B or both. If one or the other is missing, the "worker" can use a less efficient method to generate class A from class B, and class B using repeated calls to the delegate that returns class A. – Steve Apr 17 '15 at 19:47
  • Also, I would note that the problem would re-appear in your example when public Person(Func funcA) : this(funcA, null) is added. – Steve Apr 17 '15 at 20:31