Как мне сделать деженерики для моих методов работы со структурой

Рейтинг: 0Ответов: 1Опубликовано: 24.08.2023

У меня есть структура данных:

public struct ConfigData
{
    public bool AlwaysOnTop;
    public string Test;
}

Есть какой-то такой класс:

internal class Config
{
    private readonly ConfigData _config = new ConfigData();

    public Config()
    {
        
    }

    public void SetValue(string key, dynamic value)
    {
        
    }
}

В нем я хочу типизировать для метода SetValue параметры key и value как допустим в тайп скрипте, чтобы редактор мне подсказывал какие я могу передать ключи, а когда я ввел ключ, он бы мне показал какой тип данных ожидается в value.


Вот таким способом я бы это реализовал в TypeScript:

interface IConfig
{
    alwaysOnTop: boolean;
    test: string;
}

const config: IConfig =
{
    alwaysOnTop: true,
    test: ""
};

function get<TKey extends keyof IConfig>(key: TKey): IConfig[ TKey ]
{
    return config[ key ];
}

function set<TKey extends keyof IConfig>(key: TKey, value: IConfig[ TKey ]): void
{
    config[ key ] = value;
}

Метод (функция) get:
введите сюда описание изображения
И set:
введите сюда описание изображения


Я немного по лучше подумал и думаю, что лучшим вариантом будет использовать Property с get/set. Но вопрос все же интересен.

Ответы

▲ 2

Прям то, что вы хотите сделать, скорей всего не получится добиться на языке C#, ибо он строго типизированный, и тут строго работают с конкретными объектами, а не с некой абстракцией. Вы можете написать свой "костыль", который будет это все обрабатывать, и задавать нужные значения, находя через рефлексию по имени нужные свойства/поля, но кода там будет довольно прилично, и будет довольно запутанный.

Как альтернатива, могу вам предложить стандартный механизм, который используется много где в самом C#, а именно установка настроен через простой Action:

Допустим у нас есть такой класс:

public class Config
{
    public string Test { get; set; }
    public bool AlwaysOnTop { get; set; }
}

Наша задача инициализировать его, задав настройки через метод. Пишем, например, следующее:

public Config InitConfig(Action<Config> options = null)
{
    var config = new Config();
    options?.Invoke(config);
    return config;
}

Это нам позволяет написать так:

var config = InitConfig();

и на выходе будет проинициализированный объект настроек, но мы можем еще написать так:

var config = InitConfig(options =>
{
    options.AlwaysOnTop = true;
    options.Test = "123";
});

и вот у нас уже наш класс с нужными нам настройками.
Тут как видите все очень просто, мы вызываем у Action метод Invoke, передавая в него экземпляр нужного класса, он сам своей "магией" пропишет все нужные значения в класс.

И вот понимая этот принцип, вы можете пойти дальше, например, написать метод расширения, перенеся туда этот метод, и запрашивать в нем уже проинициализированный тип, получим такое:

public static class Extensions
{
    public static Config SetConfig(this Config config, Action<Config> options = null)
    {
        options?.Invoke(config);
        return config;
    }
}

Ну и использование будет таким:

var config = new Config
{
    Test = "123"
};

config = config.SetConfig(options => options.AlwaysOnTop = true);

Заметьте, я методом меняю только одно значение, и за нас Invoke автоматически все подставит, и на выходе будет класс и со старыми значениями, и с теми, что указали через метод.

Ну а дальше уже дело фантазии, внедряйте это как ходите, подстраивайте под свои задачи. Да это не совсем то, что вы хотите, но в целом, думаю отличная замена. Удачи!