Associate a value with a subclass in c#
I have the following class and my question lies with it's GetComponent function
class GameObject
{
private List<GameComponent> _components = new List<GameComponent>();
public void AddComponent(GameComponent component)
void RemoveComponent(string name)
public T GetComponent<T>(string name) where T : GameComponent
}
class GameComponent
{
public string Name { get; set; }
}
So in GetComponent i want to get the component identified by name
and return it as T.
So i would call
NavigationComponent navigation = gameObject.GetComponent<开发者_JAVA技巧NavigationComponent>("navigation");
where NavigationComponent would be a subclass of GameComponent
Since each NavigationComponent instance is only ever going to have the same name, "navigation", i am pondering if i can do something to simplify the GetComponent to the following
NavigationComponent navigation = gameObject.GetComponent<NavigationComponent>();
Thanks in advance.
Update 1
Well i should mention that the GameObject works like a set of GameObjects, identified by their name, so there can be only one of each in it.
So to elaborate lets say we have
class SimpleNavigation : GameComponent {...}
class AdvancedNavigation : GameComponent {...}
class SensorSystem : GameComponent {...}
Then i would give SimpleNavigation and AdvancedNavigation the Name "navigation" and to the SensorSystem the name "sensor"
So i cannot have both SimpleNavigation and AdvancedNavigation in the above example.
Hope that makes sense.
Thanks in advance.
p.s. I fear that i might be placing too many constrains when there isn't much to gain and longterm the specifications might change, but that is outside of the scope of the question i guess
Firstly, if all NavigationComponents will have the same name, how will you distinguish between them? If you only want to return a single NavigationComponent, you will need a unique identifier.
You can use the OfType() method to return all instances of a type:
var navigationComponents = _components.OfType<NavigationComponent>();
From there, you can select a single NavigationComponent by some unique identifier:
NavigationComponent nc = navigationComponents.Where(n => n.Id == 1);
Rewritten:
You've mentioned that .Name
should remain as a property on your GameComponent
class--so one option to consider is overriding the equality operator to say "if two instances of GameComponent
have the same name then they should be considered equal". Combined with Dave's suggestion of using OfType()
you would have something like this:
class GameObject
{
private List<GameComponent> _components = new List<GameComponent>();
public T GetComponent<T>(string name) where T : GameComponent, new
{
return
(from c in _components.OfType<T>()
where c == new T { Name = name }
select c).FirstOrDefault();
}
}
class GameComponent
{
public string Name { get; set; }
public bool Equals(GameComponent other)
{
if (ReferenceEquals(null, other))
{
return false;
}
if (ReferenceEquals(this, other))
{
return true;
}
return Equals(other.Name, Name);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != typeof(GameComponent))
{
return false;
}
return Equals((GameComponent)obj);
}
public override int GetHashCode()
{
return (Name != null ? Name.GetHashCode() : 0);
}
}
It's worth thinking through whether or not you want to implement this approach--it will affect any equality-checks (not reference-checks). However it's a very powerful ability to say "X and Y should be considered equal when X"--and equality overloading lets you implement that logic in a single centralized home (right on the entity).
Update: In response to your edit:
Then i would give SimpleNavigation and AdvancedNavigation the Name "navigation" and to the SensorSystem the name "sensor"
If this is the case, then you really can't hope to have an interface like this...
public T GetComponent<T>() where T : GameComponent { }
...because multiple GameComponent
object types may potentially share the same name. So you cannot infer name from type.
Why not go with an enum
instead? Then you might have something like this:
public enum ComponentType
{
Navigation,
Sensor // etc.
}
And your GameObject
class could in turn hold a Dictionary<ComponentType, GameComponent>
.
Then your GetComponent
method could look like this:
GameComponent GetComponent(ComponentType type) { }
Anyway, it's a thought...
I think what makes more sense is this:
Firstly, if you're looking up by name, use a Dictionary<string, GameComponent>
instead of a List<GameComponent>
. This will give you O(1) lookup time instead of O(N).
Now, if you want name to be dependent on type, define your GameComponent
class like this:
class GameComponent
{
public virtual string Name
{
// You could also hard-code this or cache it
// for better performance.
get { return this.GetType().Name; }
}
}
You might then choose to override the Name
property in your derived methods, like so:
class NavigationComponent : GameComponent
{
public override string Name
{
// This returns the same thing as GetType().Name,
// but it's faster.
get { return "NavigationComponent"; }
}
}
Then your AddComponent
method would be:
public void AddComponent(GameComponent component)
{
_components.Add(component.Name, component);
}
and your GetComponent
method would be simple:
public T GetComponent<T>() where T : GameComponent
{
return _components[typeof(T).Name] as T;
}
精彩评论