Home > other >  Anyway to remove repetition for C# class properties?
Anyway to remove repetition for C# class properties?

Time:12-13

I have a class like this:

public class DbSettingsValues : Dictionary<string, string>
{

    public DbSettingsValues() { }

    public DbSettingsValues(Dictionary<string, string> values) : base(values) { }

    public string EmailApiServer
    {
        get => this.GetValueOrDefault(DbSettingsKeys.EmailApiServer, "");
        set => this[DbSettingsKeys.EmailApiServer] = value;
    }

    public string EmailApiKey
    {
        get => this.GetValueOrDefault(DbSettingsKeys.EmailApiKey, "");
        set => this[DbSettingsKeys.EmailApiKey] = value;
    }

    // about 15 more...

    // There is also a few difference, like this one:
    public int RegisterFirstEmailDelay
    {
        get => int.Parse(this.GetValueOrDefault(DbSettingsKeys.RegisterFirstEmailDelay, "24"));
        set => this[DbSettingsKeys.RegisterFirstEmailDelay] = value.ToString();
    }
}

So for every property I need to put the exact name 3 times. I understand this would be bad for maintenance later and prone to mistakes when I make more properties by copying (i.e. forget to replace the name in 1 of the 3 places).

Is there a better way to deal with this? Some solutions came to my mind:

  • Something like the old T4, I don't see the T4 menu anymore in VS2022, so I assume it doesn't work on .NET Core?

  • Code Generator seem to require an external project. I assume this is how EF Core Scaffolding works? Can someone suggest a keyword/link to what I should look for? I.e. read a C# project, look for properties inside a class and generate a .cs file accordingly?


As of current specs, the key text is just the same:

    public const string EmailApiServer = nameof(EmailApiServer);
    public const string EmailApiKey = nameof(EmailApiKey);
    // ...

So technically I can just replace DbSettingsKeys.EmailApiServer to nameof(EmailApiServer) for example, but I am not sure it will stay the same later. Is it a good idea to proceed with this now and think about it later when specs change?

Update: I just realized that even with nameof(PropertyName), it's still prone to copying issue since there is no error if I forget to change the name:

    // No error here because the name is valid
    public string EmailApiKey
    {
        get => this.GetValueOrDefault(nameof(EmailApiServer), "");
        set => this[nameof(EmailApiServer)] = value;
    }

I think something like CallerMemberNameAttribute maybe useful but I don't actually know how it works.

CodePudding user response:

If the keys really have the same names as the properties, you can indeed use [CallerMemberName] attributes to reduce the number of times that you have to type the property name.

For example:

public class DbSettingsValues : Dictionary<string, string>
{
    public string EmailApiServer
    {
        get => getProperty();
        set => setProperty(value);
    }

    public string EmailApiKey
    {
        get => getProperty();
        set => setProperty(value);
    }

    public int RegisterFirstEmailDelay
    {
        get => getProperty(24);
        set => setProperty(value);
    }

    string getProperty(string defaultValue = "", [CallerMemberName] string? propName = default)
    {
        return this!.GetValueOrDefault(propName, defaultValue);
    }

    T getProperty<T>(T defaultValue, [CallerMemberName] string? propName = default)
    {
        var result = this!.GetValueOrDefault(propName, defaultValue!.ToString());
        return (T) Convert.ChangeType(result, typeof(T))!;
    }

    void setProperty(string value, [CallerMemberName] string? propName = default)
    {
        this[propName!] = value;
    }

    void setProperty<T>(T value, [CallerMemberName] string? propName = default)
    {
        this[propName!] = value!.ToString()!;
    }
}

It's not perfect, but it does reduce the chance of typos significantly.

  • Related