Многократная отправка с использованием дженериков

Я пытаюсь абстрагироваться от реализации моего интерфейса, предоставляя фабрику / конструктор с использованием обобщений. Тем не менее, я сталкиваюсь с проблемой множественных отправок и обобщений C# во время выполнения, которые делают что-то странное.

Основной сценарий - я определил несколько интерфейсов:

public interface IAddressModel
{
}

public interface IUserModel
{
}

Затем у меня есть фабричный класс для возврата реальных реализаций:

public class Factory
{
    public T BuildModel<T>()
    {
        return BuildModel(default(T));
    }

    public object BuildModel(object obj)
    {
        //this is here because the compiler will complain about casting T to
        //the first inteface parameter in the first defined BuildModel method
        return null;
    }

    public IAddressModel BuildModel(IAddressModel iModel)
    {
        //where AddressModel inherits from IAddressModel
        return new AddressModel();
    }

    public IUserModel BuildModel(IUserModel iModel)
    {
        //where UserModel inherits from IUserModel
        return new UserModel(); 
    }
}

Проблема в том, когда фабрика называется так: new Factory().BuildModel<IAddressModel>()Метод BuildModel(...), который отправляется во время выполнения из обобщений, всегда является наименее производной формой T, в этом случае всегда объектом.

Однако, если вы позвоните new Factory().BuildModel(default(IAddressModel)); правильный метод отправляется (скорее всего потому, что это делается во время компиляции). Кажется, что динамическая диспетчеризация с обобщениями не проверяет методы для самого производного типа, даже если вызываемый метод должен быть одинаковым независимо от того, выполняется ли он во время компиляции или во время выполнения. В идеале я хотел бы сделать методы BuildModel(...) закрытыми и предоставлять только общий метод. Есть ли другой способ получить динамическую диспетчеризацию для вызова правильного метода во время выполнения? Я пытался изменить BuildModel<>() внедрение в return BuildModel((dynamic)default(T)) но это приводит к ошибке времени выполнения из-за невозможности определить, какой метод отправить. Есть ли способ сделать это с контравариантностью и большим количеством интерфейсов?

2 ответа

Решение

Вы можете сделать отправку самостоятельно, основываясь на типе аргумента. T:

public class Factory
{
    private Dictionary<Type, Func<object>> builders = new Dictionary<Type, Func<object>>
    {
        { typeof(IAddressModel), BuildAddressModel },
        { typeof(IUserModel), BuildUserModel }
    };

    public T Build<T>()
    {
        Func<object> buildFunc;
        if (builders.TryGetValue(typeof(T), out buildFunc))
        {
            return (T)buildFunc();
        }
        else throw new ArgumentException("No builder for type " + typeof(T).Name);
    }

    private static IAddressModel BuildAddressModel()
    {
        return new AddressModel();
    }

    private static IUserModel BuildUserModel()
    {
        return new UserModel();
    }
}

Текущее состояние кода требует явного преобразования для компиляции.

public T BuildModel<T>()
{
    return (T)BuildModel(default(T));
}

BuildModel рассматривает T полиморфно как объект. BuildModel не знает, что Т является IAddressModel или IUserModel если вы не определите такое ограничение:

public T BuildModel<T>() where T: IAddressModel
{            
    Console.WriteLine(typeof(T));
    return (T)BuildModel(default(T));
}

Теперь компилятор имеет достаточно информации, чтобы признать, что T является IAddressModel, Но то, что вы ищете, для object стать более производным параметром (ковариантным), который не является типобезопасным. Другими словами, C# не поддерживает ковариантные типы параметров, потому что он не является безопасным типом.

Вы все еще можете достичь фабричного поведения с помощью условной логики:

    public T BuildModel<T>()
    {
        T result = default(T);

        if (typeof(T) == typeof(IAddressModel))
            result = (T)BuildModel((IAddressModel)result);
        else if (typeof(T) == typeof(IUserModel))
            result = (T)BuildModel((IUserModel)result);

        return result;
    }
Другие вопросы по тегам