Примените универсальный посетитель к универсальному производному классу неуниверсального базового класса

В настоящее время у меня есть что-то вроде этого:

public abstract class Base {...}
public class Derived<T> : Base {...}

class Visitor {
    public static void Visit<T>(Derived<T> d) {
        ...
    }
}

Мой вопрос, учитывая Base ссылка, которую я знаю, это Derived Например, как я могу применить это Visit функция к этому объекту, используя правильный универсальный экземпляр? Я понимаю, что ответ, вероятно, будет включать в себя динамическое понижение с проверкой типов, чтобы убедиться, что объект не является каким-то другим типом, полученным из base, что вполне нормально. Я предполагаю, что ответ включает в себя рефлексию, что тоже хорошо, хотя я бы предпочел, чтобы был способ сделать это без рефлексии.

Также хорошо, если ответ включает абстрактный метод Base а также Derived; У меня достаточно контроля над классами, чтобы добавить это. Но в конце дня мне нужно вызвать обобщенную функцию, правильно созданную с помощью T Производного типа.

Извините, если это простой вопрос; Я пришел из C++, где мой инстинкт был бы использовать CRTP или что-то подобное, что невозможно в C#.

РЕДАКТИРОВАТЬ:

Вот пример того, что мне нужно сделать:

Base GetSomeDerivedInstance() { ...; return new Derived<...>(); }
var b = GetSomeDerivedInstance();

// This is the line that needs to work, though it doesn't necessarily
// need to have this exact call signature. The only requirement is that
// the instantiated generic is invoked correctly.
Visitor.Visit(b);

4 ответа

Решение

По моему мнению, ответы, включающие двойную диспетчеризацию и дополнительные классы, будут лучше, чем использование рефлексии, чтобы сделать то, что наследование должно сделать для вас.

Обычно это означает определение метода accept в доступном классе, который просто вызывает правильный метод Visit у посетителя.

class Base
{
    public virtual void Accept(Visitor visitor)
    {
        visitor.Visit(this); // This calls the Base overload.
    }
}

class Derived<T> : Base
{
    public override void Accept(Visitor visitor)
    {
        visitor.Visit(this); // this calls the Derived<T> overload.
    }
}

public class Visitor
{
    public void Visit(Base @base)
    {
        ...
    }

    public void Visit<T>(Derived<T> derived)
    {
        ...
    }
}

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

Base b = createDerived();
b.Accept(new Visitor());

Если ваш метод посещения является статическим классом, который вы не можете изменить по какой-либо причине, вы всегда можете заключить его в фиктивный класс посетителя, который вызывает правильный статический метод.

(Отредактировано для ясности)

В следующем примере будет использоваться переменная с именем "anyvalue", тип которой известен только во время выполнения. Затем мы создадим экземпляр вашего производного класса на основе типа anyvalue. Получив этот экземпляр, мы можем использовать отражение, чтобы получить правильный метод Visit.

var anyvalue = 5; // This value could have come from anywhere. 
...    
var derivedType = typeof (Derived<>).MakeGenericType(anyvalue.GetType());
var dvalue = Activator.CreateInstance(derivedType);
var method = typeof(Visitor).GetMethod("Visit");
var genericMethod = method.MakeGenericMethod(new[] { anyvalue.GetType() });
genericMethod.Invoke(null, new [] { dvalue });

Что немного сбивает с толку, так это то, что это скелетный пример, и вы не используете исходное значение ни для чего, кроме получения типа времени выполнения. В реальной реализации я бы предположил, что конструктор будет использовать это значение для установки внутреннего состояния в экземпляре Derived. Это не рассматривается здесь, потому что это не часть вопроса, который был задан.

ОБНОВИТЬ:

Я думаю, что это будет делать то, что вы хотите. Обратите внимание, что я создал itemarray, чтобы у нас были некоторые значения времени выполнения. Они должны быть созданы где-то. Поэтому, независимо от того, передаются ли они как object[] или предоставлены каким-либо иным способом, они должны быть где-то созданы с указателем типа. Кроме того, это предполагает, что Derived<> является единственным производным классом. В противном случае это не безопасный код.

var itemarray = new Base[] { new Derived<int>(), new Derived<string>() };

foreach (var baseObject in itemarray)
{
    var derivedType = baseObject.GetType();
    var visitMethod = typeof(Visitor)
        .GetMethod("Visit")
        .MakeGenericMethod(derivedType.GetGenericArguments());
    visitMethod.Invoke(null, new[] { baseObject });
}

Подход Accept кажется более управляемым. Моя цель состояла в том, чтобы ответить на вопрос, который вы задали, не вынося суждения о вашем подходе. Мне нужно было использовать этот подход несколько раз. Я написал структуру сущностей около 9 лет назад. Мне было очень тяжело делать именно то, что вы пытаетесь сделать. Я создал базовые классы, которые не были универсальными, чтобы я мог совместно использовать основные функции независимо от универсального типа. Это оказалось сложным. Я не уверен, что сделал бы то же самое сейчас. Я бы, вероятно, исследовал несколько паттернов так же, как и вы.

Вы должны быть в состоянии сделать что-то вроде следующего:

Base foo = new Derived<int>();

var method = typeof(Visitor).GetMethod("Visit", BindingFlags.Public | BindingFlags.Static);
method.MakeGenericMethod(foo.GetType().GenericTypeArguments.First()).Invoke(null, new[] {foo});

Возможно, вы имеете в виду нечто подобное?

public class Derived<T>
{
}

public abstract class Derivable<T>
{
    public Derived<T> CreateDerived()
    {
        return new Derived<T>();
    }
}

public class Foo : Derivable<Foo>
{
}

class Visitor
{
    public static void Visit<T>(Derived<T> obj)
    {
        Console.Out.WriteLine("Called!");
    }
}

void Main()
{
    var obj = new Foo();
    var derived = obj.CreateDerived();
    Visitor.Visit(derived);
}

Если создание Derived<T> является T-конкретно, тогда вы бы сделали CreateDerived метод абстрактный и реализовать его для каждого T, Или используйте IDerivable<T> интерфейс вместо, если вы не хотите его в качестве базового класса.

Другие вопросы по тегам