Event Inheritance with C#8 Default Interface Implementation/Traits
Asked Answered
F

1

6

There is currently little documentation surrounding the limitations of events with the new C#8 default interface implementations (traits). I am particularly confused with the spec proposal. Not only is the example given invalid C# (the "override" event is missing an identifier), but implementing any of these in C#8 (VS2019, .NET Core 3.0) returns a host of compiler exceptions. In addition, the release notes for C#8 don't make any mention of events for interface traits. As I continued to try and track down an answer, I also couldn't gather anything useful from the open issues list.

So the questions are: is this feature implemented and usable? If so, what is the proper syntax?

Flaherty answered 11/11, 2019 at 6:31 Comment(5)
You can't use events with DIMs. I bet you wanted to implement your own INPC trait?Mayle
It is purely a refactoring feature, helps dealing with having to make a change in an interface but not being able to modify external code. Events do not make sense, such code can never raise the event.Succinct
@HansPassant it's not only about versioning, it's also about traits. OK, the real reason is compatibility with the Android SDK which uses default members for both. An INPC trait makes sense but isn't possible right now. I bet if Java had events, it would be implemented tooMayle
This is definitely not a typo or something that can't be reproduced. It's a known issue, and indirectly mentioned in the language design meetingsMayle
@PanagiotisKanavos I did, lol. Unsuccessfully, though :(Ordinand
M
6

Default interface members are used for traits, not just versioning, and an INPC trait would make sense.

Unfortunately, it's not possible to use DIMs to raise events right now, and implementing this seems to be a pain - it would require overhauling the events mechanism and break a ton of code, especially library code. We can use DIMs to add or remove handlers, but that's not so useful.

It would be nice to have something like :

interface InpcTrait : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private T Set(T value,String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        return value;
    }
}

class Customer
{
    private string _name;
    public string Name 
    {
        get=>_name;
        set=>_name=Set(value,"Name");
    }
}


Unfortunately, this isn't possible. That's because the event keyword in a class generates a backing field that holds the event handler and add/remove accessors. When we raise the event, we call that event handler.

Interfaces can't have state, which means we can't access that event to raise it.

When we specify an event in an interface, we create a virtual event and the compiler only allows adding/removing event handlers to it. Raising the interface still requires access to the backing field.

This Sharplab.io example shows that :

public class DemoCustomer : INotifyPropertyChanged
{
    // These fields hold the values for the public properties.
    private Guid idValue = Guid.NewGuid();
    private string customerNameValue = String.Empty;
    private string phoneNumberValue = String.Empty;

    public event PropertyChangedEventHandler PropertyChanged;

    private void NotifyPropertyChanged(String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Generates

    [CompilerGenerated]
    private PropertyChangedEventHandler m_PropertyChanged;

    public event PropertyChangedEventHandler PropertyChanged
    {
        [CompilerGenerated]
        add
        {
            //some code
        }
        [CompilerGenerated]
        remove
        {
            //some code
        }
    }

    private void NotifyPropertyChanged(string propertyName = "")
    {
        if (this.m_PropertyChanged != null)
        {
            this.m_PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

What we can do, is add or remove event handlers, but we can't even check whether the event already has other handlers. We risk adding the same event handler multiple times.

This is valid :

interface INPCtrait:System.ComponentModel.INotifyPropertyChanged
{            
    private  void AddSomeDefaultHandler()
    {
       PropertyChanged+=Something;
    }

    private  void RemoveDefaultHandler()
    {
       PropertyChanged-=Something;
    }

    public void Something(Object sender,System.ComponentModel.PropertyChangedEventArgs args)
    {
    }    
}

But we have no way of knowing whether we need to add that default handler or not.

Mayle answered 11/11, 2019 at 10:6 Comment(1)
Ah, this makes sense, thank you! You are indeed correct that INPC is the use case I was looking for. It would be nice to avoid mandating inheritance, but sounds like sticking with an abstract base class is going to continue to be the standard moving forward.Flaherty

© 2022 - 2024 — McMap. All rights reserved.