Search code examples
c#abstract-classpartial-classessourcegenerators

Source Generators: How can I prevent the instantiation of a base class declared as "partial" in C#?


Summary

I have a base class which inherits from an abstract class, but must itself be declared as partial, because I'm using Source Generators to generate parts of the implementation.

How can I prevent the instantiation of that partial class in C#? Is there some way to do this at compile-time?

Edit: I'm using C# 8.0 (.NET Standard 2.1) with a Xamarin.Forms project.

I've tried partial abstract, but that isn't allowed.

TL;DR

In one of my projects, I have been using an abstract class that serves as a base class for ViewModels to implement the MVVM pattern. Previously, said base class implemented the INotifyPropertyChanged interface, but now I am switching to the CommunityToolkit.Mvvm in order to be able to use the MVVM Source Generators (which I have used in other projects extensively already, I even wrote a blog series about them).

Apparently, in C#, a class can only be either partial or abstract but not both at the same time, which makes sense. Since the Source Generators work with partial classes only, my base class cannot be abstract anymore. However, this creates the problem that I cannot prevent the base class from being instantiated at compile-time. While it was still an abstract class, it couldn't be instantiated, but now it's possible to do so.

Abstract base class

This is what the base class used to look like (reduced/modified example):

public abstract class ViewModelBase : INotifyPropertyChanged
{
    private bool _myBool;
    public bool MyBool
    {
        get => _myBool;
        set => SetField(ref _myBool, value);
    }

    private ICommand _myCommand;
    public ICommand MyCommand => _myCommand ??= new Command(MyMethod);

    private void MyMethod()
    {
        // ...
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value)) return false;
        field = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

Partial base class

The changed base class looks like this now:

public partial class ViewModelBase : ObservableObject
{
    [ObservableProperty]
    private bool _myBool;

    [RelayCommand]
    private void MyMethod()
    {
        // ...
    }
}

It's much smaller, as expected, but now it can be instantiated, which is something I would like to prevent.


Solution

  • You can declare partial classes as abstract, but note that order of keywords is important (otherwise it will not compile):

    public abstract partial class ViewModelBase : ObservableObject
    {
        // ...
    }
    

    As per spec - modifiers like abstract, sealed, etc should precede the partial keyword.