简体   繁体   中英

Apply generic visitor to generic derived class of non-generic base class

Currently I have something like this:

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

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

My question is, given a Base reference that I know is a Derived instance, how can I apply that Visit function to that object, using the correct generic instantiation? I understand that the answer will probably involve a type-checked dynamic downcast, to make sure that the object isn't some other type derived from base, which is all fine. I assume the answer involves reflection, which is also fine, though I'd prefer if there was a way to do it without reflection.

It's also ok if the answer involves an abstract method on Base and Derived ; I do have enough control of the classes to add that. But at the end of the day, I need to call a generic function, correctly instantiated with the T of the Derived type.

Sorry if this is an easy question; I come from a C++ background, where my instinct would be to use a CRTP or something else like that, which isn't possible in C#.

EDIT:

Here's an example of what I need to be able to do:

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);

(Edited for clarity)

The following will use a variable called "anyvalue" whose type is only known at run-time. Then we'll create an instance of your Derived class based on the type of anyvalue. Once we have that instance, we can use reflection to get the correct Visit method.

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 });

What is a little confusing is that this is a skeletal example and you do not use the original value for anything other than getting a run-time type. In the real world implementation, I would assume a constructor would use that value to set internal state in the Derived instance. That is not covered here because that is not part of the question that was asked.

UPDATE:

I think this will do what you want. Note that I created the itemarray so that we would have some run-time values. They have to be created somewhere. So whether they are passed in as object[] or provided some other way, they had to be constructed with a type specifier somewhere. Also, this assumes that Derived<> is the only derived class. Otherwise, this is not safe code.

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 });
}

The Accept approach does seem a bit more manageable. My goal was to answer the question you asked without passing judgment on your approach. I have needed to use this approach several times. I wrote an entity framework about 9 years ago. I had a really hard time doing exactly what you are trying to do. I created base classes that were not generic so that I could share basic functionality regardless of the generic type. It proved challenging. I am not sure I would do it the same way now. I'd probably investigate a few patterns just as you are doing.

In my opinion, answers involving double-dispatch and More Classes are going to be superior than using reflection to do what inheritance should do for you.

Normally this means defining an 'accept' method on the visitable class, which simply calls the correct Visit method from the visitor.

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)
    {
        ...
    }
}

Then you can do what you mentioned in your question, with a small modification:

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

If your visit method is a static class that you can't change for whatever reason, you could always wrap this into a dummy instance visitor class which calls the right static method.

You should be able to do something like the following:

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});

Perhaps you're meaning something like this?

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);
}

If the creation of the Derived<T> is T -specific, then you'd make the CreateDerived method abstract and implement it for each T . Or use an IDerivable<T> interface instead if you don't want it as your base class.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM