BaseClass method that returns an arbitrary subclass of BaseClass

In my game, I have a base class Loot, which has universal methods for everything that can be captured by the player and stored in his inventory. This will include potions, equipment, ammunition, etc. I can equip arrows, but not potions. Thus, Arrow will be a subclass of Ammo, which will ultimately be obtained from Loot. I can drink the potion, but not the arrow. Thus, Potion will subclass Loot, but implement IConsumeable. Etc.

Loot objects have the Quantity property (10 arrows, 2 potions). In my Loot class, I have a Split method that allows a player to take a β€œstack” of items (like arrows) and split it into two separate stacks. Therefore, it reduces the number of Arrow instances by a certain amount, and then returns a new Arrow instance with the value Quantity = =, which was taken from the original instance.

My idea was that I would write a method in Loot, since any Loot can be stacked if its int StackLimit property is greater than 1. After decreasing the calling Loot by the specified number, I will return a new object of the same type. The problem is that I do not know what type of subclass Loot this object will have.

public abstract class Loot { public int Quantity { get; set; } public Loot Split(int quantityToTake) { Loot clone = (Loot)this.MemberwiseClone(); //RestrictNumberToRange(int min, int max, int value) is a delegate which clamps a value to min,max this.Quantity -= Utility.RestrictNumberToRange<int>(1, this._quantity - 1, quantityToTake); clone.Quantity = quantityToTake; return clone; } } 

Is this really a bad way? I was thinking about reflection, but I hear mixed opinions about whether to use it in such a case.

Is there no way to determine a method for this. FurthestSubClass?

I know that my subclasses may have different constructors, so it is probably not possible to try and return "this this.FurthestSubclass ()" because I don’t know how to build it. But I would like to deal with its Loot methods, so I use Loot for the return type.

+4
source share
2 answers

I think this is a good example for generics. Try rewriting your split method in Loot as follows:

 public TLoot Split<TLoot>(int quantityToTake) where TLoot : Loot { TLoot clone = (TLoot)this.MemberwiseClone(); ... return clone; } 

This should take care of typing questions.

EDIT ADD: The problem with the constructor is a bit more interesting, but you may find parameterless constructors useful in combination with object initializers. If you change the restriction as follows:

 public TLoot Split<TLoot>(int quantityToTake) where TLoot : Loot, new(TLoot) { // stuff TLoot newLoot = new TLoot(); ... return newLoot; } 

The "new (T)" constraint allows you to create new objects based on a common type.

FURTHER IMAGE: I should give an example of an object initializer in this context:

 TLoot newLoot = new TLoot { Quantity = quantityToTake }; 

This suggests that Loot has a public property called Quantity. Object initializers can set values ​​for any public property that has a public set{};

+1
source

If I'm right, the only small drawback to this is that you are forced to explicitly indicate which type of TLoot you are splitting.

 Arrow arrow1; /* some code initializing arrow1 */ Arrow arrow2 = arrow1.Split<Arrow>(10); 

Obviously, arrow1 is Arrow , we could not write anything else (for example, Arrow b = a.Split<Potion>(10); which makes no sense), but Arrow arrow2 = arrow1.Split(10); unfortunately, it will not compile, since the return type of the general method cannot be inferred if it does not have parameters of the same type.

Here's the trick: make the Split extension method on Loot . Loot

Its prototype becomes public static TLoot Split<TLoot>(this TLoot item, int quantityToTake) where TLoot : Loot and TLoot now takes place among the parameter in position 1 (even if it is entered with the special keyword this : nevermind!). This first parameter disappears when called, so that everything happens as if we had the opposite type of output.

Now you can write Arrow arrow2 = arrow1.Split(10); . This is completely legal, and the return type is an authentic strongly typed Arrow (you can even write var arrow2 = arrow1.Split(10); and then check the type of arrow2 to make sure).

I admit that in this context it looks cooler and more elegant than really useful. But I am fond of runaway interface (chaining method), and there it really becomes a huge improvement. Compare these 2 ads:

 /*Given an certain among of arrows found in the treasure chamber, each player in the team receives a fraction of it according to his age and experience, but always 10 arrows at least anyway. Ok, ok ! I had to search before finding such an example vaguely accurate in the context of arrows and potions ! lol*/ foreach(Player p in Team) p.Arrows = Treasure.Arrows.Split<Arrow>(10).Split<Arrow>(p.Age).Split<Arrow>(p.Experience*2); foreach(Player p in Team) p.Arrows = Treasure.Arrows.Split(10).Split(p.Age).Split(p.Experience*2); 

Well, here the advantage remains unsatisfactory ( Treasure.Arrows.Split<Arrow>(10+p.Age+p.Experience*2) works the same way and shorter!), But there really are situations in which the return type implicitly improves readability.

+1
source

All Articles