简体   繁体   中英

Adding a generic method constraint from the another generic class

I'm not sure the title reflect the question that I was meant, but..
Let's say I have two classes, Entity and Component :

public abstract class Entity
{
    private List<Component> _components = new List<Component>();

    public void AddComponent<T>()
        where T : Component
    {
        T component = (T)Activator.CreateInstance(typeof(T));
        component.Owner = this;

        _components.Add(component);
    }
}

public abstract class Component
{
    public Entity Owner { get; protected set; }

    public abstract void Update();
}

As you may notice, above classes are abstract classes which mean is not intended for direct use. However, on the later stage of development, I'm aware that some Component require ability that only attachable / Added by specific class that inherited to Entity class.

So, I added a class Component<T> that inherit Component :

public abstract class Entity
{
    private List<Component> _components = new List<Component>();

    public void AddComponent<T>()
        where T : Component
    {
        T component = (T)Activator.CreateInstance(typeof(T));
        component.Owner = this;

        _components.Add(component);
    }
}

public abstract class Component
{
    public Entity Owner { get; protected set; }

    public abstract void Update();
}

public abstract class Component<T> : Component
{
    // I hide the base.Owner with new keyword
    // feel free to suggest me in case there is better approach to do this
    new public T Owner 
    { 
        get { return (T)base.Owner; } 
        protected set { base.Owner = value; }
    }
}

And now, let's say I have Foo , Bar and Processor class:

public class Foo : Entity
{
    public int FooValue { get; set; }
}

public class Bar : Entity
{
    public int BarValue { get; set; }
}

public class Processor : Component<Foo>
{
    public override void Update()
    {
        Owner.FooValue = 10;
    }
}

What I want to do is to make Processor class only add-able by Foo object. Currently AddComponent ignore it, so I don't know how to do that:

var foo = new Foo();
var bar = new Bar();

foo.AddComponent<Processor>(); // OK
bar.AddComponent<Processor>(); // Compiler should give an error at this point

I also tried to do this:

public void AddComponent<T, X>()
    where T : Component<X>
    where X : Entity
{
    T component = (T)Activator.CreateInstance(typeof(T));
    component.Owner = this;

    _components.Add(component);
}

However, it require me to explicitly specify the X constraint:

foo.AddComponent<Processor, Foo>();
bar.AddComponent<Processor, Bar>(); // Error, but the syntax is weird!

Any ideas?

Your post isn't clear on what constraints, if any, you have on your basic Entity and Component classes. So I don't know if the below will be feasible in your scenario. That said, I believe that if it's not, you won't be able to do what you want because otherwise the generic type parameters won't be known by the compiler.

The solution, absent any other constraints, is to make your Entity class generic, and provide the sub-class type itself as the type parameter:

class Entity { }

class Entity<T> : Entity where T : Entity<T>
{
    public void AddComponent<U>(U value) where U : Component<T> { }
}

class Component<T> where T : Entity { }

class Foo : Entity<Foo> { }

class Bar : Entity<Bar> { }

class P : Component<Foo> { }

I know it looks weird. But you're basically asking for a self-referential graph of generic type dependencies, and in C# code the above is what that looks like.

You can call the AddComponent() method using type inference (so no generic parameter needed). If you try to call it with the wrong type of Component<T> object, you'll get a compiler error:

Foo foo = new Foo();
Bar bar = new Bar();
P p = new P();

foo.AddComponent(p);
bar.AddComponent(p); // CS0311


Note: I would strongly recommend against hiding class members. It doesn't really affect your question as stated (ie you could have left that detail out completely), but having two different properties with the same name is just asking for bugs. If you must use hiding, IMHO you should at least have the new property use the hidden property. Eg:

class Component
{
    public Entity Owner { get; protected set; }
}

class Component<T> : Component where T : Entity
{
    new public T Owner
    {
        get { return (T)base.Owner; }
        set { base.Owner = value; }
    }
}

You won't get compile-time checking on assignments to the non-generic Component.Owner property, but at least you'll get a run-time error if some code tries to dereference the Owner property as the generic version, if and when the wrong type was assigned by the base type for some reason.

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