2012-07-15 6 views
5

Estoy tratando de crear un simple "inventario" de artículos, muy parecido a cualquier juego de rol. Tengo muy básico clases hechas, que tienen propiedades.C# List <baseClass> - ¿alguna forma de almacenar clases heredadas también?

De todos modos, tengo una clase base de item y heredando de eso es weapon. item tiene propiedades (nombre, valor, peso, "elemento importante") que también se usan en weapon, pero weapon tiene propiedades adicionales (ataque, defensa, velocidad, lateralidad).

Tengo el siguiente código (lo siento si la legibilidad es horrible):

static void Main(string[] args) 
{ 
    List<item> inventory = new List<item>(); 
    inventory.Add(new weapon("Souleater", 4000, 25.50f, false, 75, 30, 1.25f, 2)); 
          //item---------------------------> weapon---------> 

    Console.Write("Name: {0}\nValue: {1}\nWeight: {2}\nDiscardable: {3}\nAttack: {4}\nDefense: {5}\nSpeed: {6}\nHandedness: {7}", 
      inventory[0].Name, inventory[0].BValue, inventory[0].Weight, inventory[0].Discard, 
      inventory[0].Atk, inventory[0].Def, inventory[0].Speed, inventory[0].Hands); 

    Console.ReadLine(); 
} 

Básicamente, lo que estoy tratando de hacer es añadir una nueva weapon al inventario, pero el inventario es una List<item> tipo. Salí en un capricho con la esperanza de que, debido a su herencia, sería aceptado. Era, pero las propiedades específicas de weapon no son capaces de ser visitada:

('shopSystem.item' no contiene una definición para 'Atk' y no método de extensión 'Atk' aceptar un primer argumento de tipo 'shopSystem.item' se puede encontrar)

Entonces, ¿hay alguna manera de lograr lo que pretendía aquí? ¿Tiene un "inventario" que puede almacenar objetos item, así como objetos weapon, armour, accessory etc., que heredan de item? También vale la pena mencionar que puedo acceder a todas las propiedades deseadas si declaro lo siguiente:

weapon Foo = new weapon("Sword", 200, 20.00f, false, 30, 20, 1.10f, 1); 

Muchas gracias por su lectura.

Aquí está la item y weapon clases, si alguien está interesado:

class item 
{ 
    #region Region: Item Attributes 
    protected string name = ""; 
    protected int baseValue = 0; 
    protected float weight = 0.00f; 
    protected bool noDiscard = false; 
    #endregion 

    public item(string n, int v, float w, bool nd){ 
     name = n; baseValue = v; weight = w; noDiscard = nd;} 

    public string Name{ 
     get{return name;} 
     set{if(value != ""){ 
      name = value;} 
     }//end set 
    } 

    public int BValue{ 
     get{return baseValue;} 
    } 

    public float Weight{ 
     get{return weight;} 
    } 

    public bool Discard{ 
     get{return noDiscard;} 
    } 
} 

class weapon : item 
{ 
    #region Region: Weapon Attributes 
    private int atk = 0; 
    private int def = 0; 
    private float speed = 0.00f; 
    private byte hands = 0; 
    #endregion 

    public weapon(string n, int v, float w, bool nd, int a, int d, float s, byte h) : base(n, v, w, nd){ 
     atk = a; def =d; speed = s; hands = h;} 

    public int Atk{ 
     get{return atk;} 
    } 

    public int Def{ 
     get{return def;} 
    } 

    public float Speed{ 
     get{return speed;} 
    } 

    public byte Hands{ 
     get{return hands;} 
    } 

} 

Respuesta

8

Con el fin de acceder a las propiedades específicas de la clase heredada, es necesario emitir el tipo de clase de cemento correspondiente. Por lo tanto, en lugar de acceder al inventory[0], debe acceder al (weapon)inventory[0].

Además, si está realizando tareas comunes que tendrán diferentes implementaciones basadas en la clase subyacente, como registrar los valores en la consola (que me doy cuenta que se hizo para probar, pero de todos modos es un buen ejemplo), debería implementar un método invalidable en la clase base.

Por ejemplo, en la clase base:

public virtual void LogProperties() 
{ 
Console.Write("Name: {0}\nValue: {1}\nWeight: {2}\nDiscardable: {3}\nAttack", 
      this.Name, this.BValue, this.Weight, this.Discard); 
} 

Y en la clase de arma:

public override void LogProperties() 
{ 
Console.Write("Name: {0}\nValue: {1}\nWeight: {2}\nDiscardable: {3}\nAttack: {4}\nDefense: {5}\nSpeed: {6}\nHandedness: {7}", 
      this.Name, this.BValue, this.Weight, this.Discard, 
      this.Atk, this.Def, this.Speed, this.Hands); 
} 

Luego, para acceder a este:

inventory[0].LogProperties(); 
+1

Si uso el inventario '(weapon) [0] .Atk', sigo recibiendo el mismo mensaje de error. – Deadrust

+1

Intenta usar ((armamento) inventario [0]). Atk en cambio –

+0

Sí, ¡eso funciona! ¡Gracias! – Deadrust

0

se puede implementar un común interfaz (por ejemplo, IItem) a través de la cual consulta las propiedades de los artículos, o implementa una clase base común, pro bably uno abstracto, y usarlo como el tipo T del List. Si usa la última opción, asegúrese de anular las funciones de la clase base cuando corresponda, el polimorfismo hará el resto.

0

Deberá devolver el valor a weapon para acceder a esas propiedades, es decir, ((weapon)inventory[0]).Atk). Tenga en cuenta los paréntesis: el lanzamiento tiene menor precendencia que el acceso de campo, por lo que no puede escribir (weapon)inventory[0].Atk; eso se interpretaría como (weapon)(inventory[0].Atk).

También necesitará distinguir entre las diferentes clases (por ejemplo, probablemente también tenga una clase armor). Lo más fácil de hacer sería agregar un valor extra en la clase base item que le indica el tipo real del objeto y luego emitir en consecuencia.

Sin embargo, un mejor enfoque sería usar la herencia; deje que item defina una serie de métodos que puedan manejar todos los elementos genéricos, p. Ej. mostrar información sobre el elemento y luego anularlo en las clases heredadas. Es decir, en lugar de acceder a todos esos datos directamente, llama a un método que lo combina de una manera sensata. Para su ejemplo particular, que está convirtiendo el tema en una cadena, por lo que probablemente tiene sentido para anular ToString() - que significa que sería capaz de hacer esto:

Console.Write(inventory[0]); 

y dependiendo de si es un elemento o un arma, se llamará al ToString() de la clase apropiada (normalmente, tendría que llamar al método en sí, pero ToString() es especial porque todos los objetos tienen ese método, por lo que Console.Write lo hace por tú).

En su clase de arma, tendría luego implementar el método ToString así:

public string ToString() { 
    //Put all of the data in here, like in your example 
    return String.Format("Name: {0}\n...", name, ...); 
} 
+0

Me gusta el método ToString. Sin embargo, ¿qué sucede si quiero acceder a solo una de esas propiedades (por ejemplo, la propiedad .Atk) para usar en una fórmula matemática? ¿Sería mejor mantenerlo como está, por ejemplo '((arma) inventario [0]). Atk'? La cadena que estaba haciendo era solo para probar que los valores se almacenaban correctamente. – Deadrust

+0

@Deadrust: Sí, puedes hacer eso, y también puedes hacer 'weapon weapon = (weapon) inventory [0];' y luego acceder a todas las propiedades en wWappon sin conversión adicional. Probablemente quieras hacerlo incluso antes, p. almacenando el arma actualmente equipada en una clase de "jugador" o "unidad", pero los juegos de rol varían según su enfoque de esto. –

+0

También tenga en cuenta que no * tiene * para usar ToString para este ejemplo. Puede tener tanto sentido definir tu propio método y llamarlo, todo depende de lo que pretendas hacer con tus clases. –

2

Usted está haciendo lo correcto mediante el uso de un List<item> para almacenar artículos y subclases de artículos!

Cuando extrae el elemento de la lista, solo tiene que hacer un poco de trabajo para probar si es weapon, u otro tipo de item.

para ver si el elemento en la posición i en la matriz es un weapon, utilice el operador as, que devolverá null si el artículo no es el tipo especificado (quizás es armadura o algún otro tipo de elemento). Esto se conoce como un molde dynamic. Funciona incluso si el artículo es null.

weapon w = inventory[i] as weapon; // dynamic cast using 'as' operator 
if (w != null) 
{ 
    // work with the weapon 
} 

Otra manera de hacer esto es comprobar el tipo usando el operador is antes de hacer un reparto static.

if (inventory[i] is weapon) 
{ 
    // static cast (won't fail 'cause we know type matches) 
    weapon w = (weapon)inventory[i];   

    // work with the weapon 
} 
+1

Mejor alternativa a la segunda prueba: 'if (inventario [i] es arma)'. Pero su primer ejemplo (con 'as') probablemente sea preferible de todos modos, ya que evita lanzar el objeto dos veces. :) –

+0

@DanJ ¡Sí, gracias! Eso también evita la necesidad del cheque nulo. Respuesta ajustada en consecuencia. :) –

+0

Ese primer ejemplo es exactamente lo que necesitaré muy pronto, cuando empiece a agregar elementos dinámicamente a la instancia de 'inventario '. ¡Muchas gracias! – Deadrust

1

Aquí es donde Linq puede ser tu amigo cuando se trata de polimorfismo. El problema con la fundición de elementos es cuando el índice dado no es del tipo correcto. (Es decir, no es un inventory[0]weapon, (weapon)inventory[0] arrojará.)

Usted puede evitar la excepción mediante el uso de:

var myWeapon = inventory[0] as weapon; 

Sin embargo a continuación, que va a hacer comprobaciones #NULL todas partes.El uso de LINQ se obtiene una gran flexibilidad:

var weapons = inventory.OfType<weapon>(); 

Desde aquí se puede utilizar Where, FirstOrDefault, etc para encontrar datos que le interese o iterar sobre las armas.

+0

+1 Este sería mi enfoque, limita la cantidad de su código que debe conocer el inventario genérico. Por ejemplo, puede escribir un método que actúe en una 'Lista ' y pasar 'inventory.OfType ()' en él. Ese método nunca debe preocuparse por la existencia de otros tipos de artículos en su inventario. –

Cuestiones relacionadas