"cannot implement interface member" error when interface and concrete are in different projects

This compiles:

public interface IMyInterface
{
    event Action<dynamic> OnSomeEvent;
}

class MyInterface : IMyInterface
{
    public event Action<dynamic> OnSomeEvent;
}

But when i separate the interface and the implementation to different projects, i get:

Accessor 'TestProject2.MyInterface.OnSomeEvent.remove' cannot implement interface member 'InterfaceNamespace.IMyInterface.remove_OnSomeEvent(System.Action)' for type 'TestProject2.MyInterface'. Use an explicit interface implementation.

This occurs only with a dynamic parameter...


Solution 1:

Good catch. This looks like it's possibly a bug in the C# compiler - I'll ping Eric Lippert to see what he thinks. (dynamic can be a bit tricksy; there may well be a perfectly good but non-obvious reason for this error.)

EDIT: The code below appears not to work after all. I could have sworn I had it working this morning... I'm very confused as to what's going on. As per Simon's comments, the code fails with a message saying it's not supported by the language.

Note that if you do use explicit interface implementation, it appears to compile just fine:

// Doesn't actually compile - see edit above
class MyInterface : IMyInterface
{
    private Action<dynamic> foo;

    event Action<dynamic> IMyInterface.OnSomeEvent
    {
        // TODO (potentially): thread safety
        add { foo += value; }
        remove { foo -= value; }
    }
}

EDIT: The rest of this answer still stands...

Note that you can't specify a field-like event as an explicitly-implemented event, i.e. this doesn't work:

event Action<dynamic> IMyInterface.OnSomeEvent;

It gives the following error message:

Test.cs(15,39): error CS0071: An explicit interface implementation of an event must use event accessor syntax

And if you just try to change to the event accessor syntax, you get the same error as the original code.

Note that changing the event to a property works fine with an auto-implemented property implementation.

Solution 2:

Thanks for posting this question, and thanks to Jon for sending it my way. I've put this in the investigation queue of one of our testers who specializes in "dynamic". We'll see if we can figure out what's going on here. It certainly smells like a bug.

In the future, consider posting stuff like this on connect.microsoft.com; that gets it to testers even faster, and gives us a better mechanism for getting more information about the issue.

Solution 3:

This answer is to elborate my thoughts on this interesting problem. Not a real answer, but a contribution to the whole discussion that is too small for a normal comment.

I checked a few things, this interface:

namespace DifferentAssemblyNamespace
{
    public interface IBar
    {
        event Action<dynamic> OnSomeEvent;
    }
}

and its implementation:

// implicit interface implementation
// generates compile error "Explicit interface implementation"
public class Foo1 : IBar
{
    private Action<dynamic> foo;
    public event Action<dynamic> OnSomeEvent
    {
        add { foo += value; }
        remove { foo -= value; }
    }
}

// implicit interface implementation
// generates compile error "Not supported by the language"
public class Foo2 : IBar
{
    private Action<dynamic> foo;

    event Action<dynamic> IBar.OnSomeEvent
    {
        add { foo += value; }
        remove { foo -= value; }
    }
}

will never work, seems that one rule is excluding the other necessary rule.

but.. if we call generics for help, and use a Type parameter instead of using dynamic directly like:

namespace DifferentAssemblyNamespace
{
    public interface IGenericBar<T>
    {
        event Action<T> OnSomeEvent;
    }
}

and its implementation.

// implicit interface implementation
public class Foo3<T> : IGenericBar<T>
{
    private Action<T> foo;

    event Action<T> IGenericBar<T>.OnSomeEvent
    {
        add { foo += value; }
        remove { foo -= value; }
    }
}

for some reason we can build (as it should) and run:

/** does build **/
IGenericBar<dynamic> f = new Foo3<dynamic>();
f.OnSomeEvent += new Action<dynamic>(f_OnSomeEvent);

seems that the Type Parameter does something extra that the compiler is happy with.

I am not sure what is going on, so I would like to know as well.

assumption, highly hypothetical (perhaps crap)

but currently I put my two cents on the comparison of types there must be made via the add/remove accessors in the linked list that holds the target/methods of the event.

I bet that the compiler falls over the problem that it cannot guarantee what dynamic is in the external assembly, thus cannot determine if an element is already in the list or not, which is necessary to add or remove them.(Hence explicit interface implementation)

We all know it is just some of a attributed object but it still seems that it needs an extra step where some strong-type is guaranteed, and that is what T does, at compile time.

/ assumption, highly hypothetical (perhaps crap)