Делегирование общего класса конкретным классам


У меня есть следующие интерфейсы:

public interface IModel
{
    ModelTypes ModelType { get; } // ModelTypes is an enum
}

public interface IModelConverter<T>
{
    byte[] ToBytes(T model);
}

Кроме того, у меня есть 3 реализации IModel: ModelA,ModelB,ModelC, и следующие классы:

public class ModelAConverter : IModelConverter<ModelA>

public class ModelBConverter : IModelConverter<ModelB>

public class ModelCConverter : IModelConverter<ModelC>

Я хочу взять IModel и преобразовать его с помощью метода ToBytes. Очевидно, я не хочу, чтобы вызывающий знал каждую реализацию конвертеров, поэтому я создал класс DelegatingConverter:

public class DelegatingConverter : IModelConverter<IModel>
{
    private readonly Dictionary<ModelTypes, IModelConverter<IModel>> _modelConverters;

    public DelegatingConverter()
    {
        _modelConverters = new Dictionary<ModelTypes, IModelConverter<IModel>>
        {
            {ModelTypes.TypeA, new ModelAConverter()}, // Argument type ModelAConverter is not assignable to parameter type IModelConverter<IModel>
            {ModelTypes.TypeB, new ModelBConverter()}, // Argument type ModelBConverter is not assignable to parameter type IModelConverter<IModel>
            {ModelTypes.TypeC, new ModelCConverter()}  // Argument type ModelCConverter is not assignable to parameter type IModelConverter<IModel>
        };
    }

    public byte[] ToBytes(IModel model)
    {
        // Here is the delegation..
        return _modelConverters[model.ModelType].ToBytes(model);
    }
}

Все идет отлично, пока я не добавлю некоторые конвертеры в словарь делегирования, _modelConverters.

Ошибка заключается в том, что:

Тип аргумента ModelXConverter не присваивается типу параметра IModelConverter<IModel>

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

public interface IModelConverter<out T>

Когда я добавляю его, возникает следующая ошибка:

Параметр должен быть безопасен для ввода. Недопустимые отклонения: тип параметра 'Т' должен быть контравариантно действителен на IModelConverter.ToBytes (T). 'T' является ковариантный.

Есть ли есть ли способ сделать его лучше? Я знаю, что могу сделать каждую реализацию ModelConverter реализацией IModelConverter<IModel>, но тогда мне нужен очевидный бросок в начале каждой реализации.

2 3

2 ответа:

И я знаю, что решение здесь должно быть с использованием ковариации на T в IModelConverter

Нет, не совсем. Это будет иметь место только в том случае, если какие-либо конкретные IModelConverter можно было бы рассматривать как более общий IModelConverter - но это не так. Если конвертер только знает, как преобразовать ModelB в байты, что вы ожидаете, что он будет делать с ModelC?

Самый простой подход, вероятно, состоит в том, чтобы написать что-то более похожее это:

// This class is general...
public sealed class DelegatingConverter<T> : IModelConverter<IModel>
    where T : IModel
{
    private readonly IModelConverter<T> originalConverter;

    public DelegatingConverter(IModelConverter<T> originalConverter)
    {
        this.originalConverter = originalConverter;
    }

    public byte[] ToBytes(IModel model)
    {
        return originalConverter.ToBytes((T) model);
    }
}

Затем:

public sealed class KnownModelConverter : IModelConverter<IModel>
{
    private static readonly Dictionary<ModelTypes, IModelConverter<IModel>>
        = new Dictionary<ModelTypes, IModelConverter<IModel>>
    {
        { ModelTypes.TypeA, new DelegatingConverter<ModelA>(new ModelAConverter()) },
        { ModelTypes.TypeB, new DelegatingConverter<ModelB>(new ModelBConverter()) },
        { ModelTypes.TypeC, new DelegatingConverter<ModelC>(new ModelCConverter()) },
    };

    public byte[] ToBytes(IModel model)
    {
        // Here is the delegation..
        return _modelConverters[model.ModelType].ToBytes(model);
    }
}

В основном ключ является приведением в DelegatingConverter<T> - вам нужно убедиться, что вы используете только экземпляры pass правильного типа для его метода ToBytes - но если предположить, что model.ModelType является правильным, это должно быть нормально. (И если это не так, то исключение, вероятно, является правильным поведением.)

Вы можете ввести приведение его явно, чтобы избежать ошибки, как

public DelegatingConverter()
{
    _modelConverters = new Dictionary<ModelTypes, IModelConverter<IModel>>
    {
        {ModelTypes.TypeA, (IModelConverter<IModel>)new ModelAConverter()},
        {ModelTypes.TypeB, (IModelConverter<IModel>)new ModelBConverter()},
        {ModelTypes.TypeC, (IModelConverter<IModel>)new ModelCConverter()}
    };
}