-1

I have the following objects

class Magazine 
{ 
    String intendedGunId {get;}//Returns some Gun.weaponID;
    int size {get;}
    //Implementation
}

class Gun 
{
    public enum FireModes
    {
        Bolt,
        Semi,
        FullAuto
    }

    public FireModes fireMode { get; private set; }

    Magazine magazine;

    public Magazine reload(Magazine newMag)
    {
        if (magazine.intendedGunId == newMag.intendedGunID)
        {
            Magazine temp = magazine;
            this.magazine = newMag;
            return temp;
        }

         return newMag;
    }

    //Other implementation
 }

class AKMag : Mag
{
   //Implementation
}

class AK : Gun
{
   //Implementation
}

I am designing a gun and a magazine that should always be used in said gun for multiple different guns.

I don't think keeping the Magazine variable as T : Magazine instead of just Magazine is a smart idea, as when reloading, nearly any magazine could be accepted, and it doesn't feel like safe code; I feel like a hacker could easily take advantage of this.

I have tried the following generic:

class Gun<T> where T : Magazine
{
    T magazine;
    //Other implementation
}

class AK : Gun<AKMag>
{

}

The problem is once I use generics, it becomes impossible to store a Gun<Magazine> variable, because at some point, the compiler will say "Can not convert from Gun<AKMag> to Gun<T> where T : Magazine.

Basically, every gun has its own mag that will only belong to it's gun. I am struggling to implement this correctly, maybe from a lack of understanding of C# generics or C# inheritance.

EDIT: With a gun generic, the following scenario does not work:

Gun<Magazine> someGun;

public void func (Gun<Magazine> gun)
{
    this.someGun = gun;
}

//In another class

AK<AKMagazine> someAK;

public void func2 ()
{
    func1 (someAK);  //Error: "Can not convert from `Gun<AKMag>` to `Gun<T> where T : Magazine`."
}

Edit: I decided that the best way to do this was to check if magazine.GetType() == newMag.GetType() whenever the mag was going to be change, however an interface may work as well.

Andrew No
  • 535
  • 6
  • 20
  • share the code that makes the compiler complain. I can't understand what you mean exactly. – disklosr Nov 14 '15 at 17:54
  • Create an `IGun` interface, implement it in `Gun` and store your guns in variable typed as `IGun`. – MarcinJuraszek Nov 14 '15 at 17:54
  • @MarcinJuraszek, may I ask you for a little bit more detail? I think I understand, but I am not sure exactly how I would do that. Ill add more information to the Gun class as I am not sure an Interface will work. – Andrew No Nov 14 '15 at 18:00
  • Maybe it helps to make `T` covariant - see http://stackoverflow.com/questions/10956993/out-t-vs-t-in-generics – Ruud Helderman Nov 14 '15 at 18:09
  • @Ruud, while that seems like it would work, it requires an interface. I'll have to try to convert it. – Andrew No Nov 14 '15 at 18:17

2 Answers2

1

What about just using plain old Interface?

    class Program
    {
        static void Main(string[] args)
        {
            Gun myGun = new AK();
            Console.WriteLine(myGun.load(new AK_Mag())); // true
            Console.WriteLine(myGun.load(new ShotGun_Mag())); // false
            func2();
        }

        static Gun someGun;
        static void func1(Gun gun)
        {
            someGun = gun; 
        }

        public static void func2()
        {
            Gun someAK = new AK();
            someAK.load(new AK_Mag());
            func1(someAK);
        }
    }

    public class AK : Gun
    {
        public bool load(Mag mag)
        {
            if (mag == null)
                return false;
            if ( mag.GetType() != typeof(AK_Mag))
            {
                return false;
            }

            return true;
        }

        public void shoot(int x, int y, int z)
        {
            throw new NotImplementedException();
        }

        public bool setFireMode(Gun.FireMode mode)
        {
            this.mode = mode;
            return true;
        }
    }

    public class AK_Mag : Mag
    {
        public int Remain()
        {
            throw new NotImplementedException();
        }
    }

    public class ShotGun_Mag : Mag
    {
        public int Remain()
        {
            throw new NotImplementedException();
        }
    }

    public interface Gun
    {
        public enum FireMode { Manual, Burst };
        bool load(Mag mag);
        void shoot(int x, int y, int z);
        bool setFireMode(FireMode mode);        
    }

    public interface Mag
    {
        int Remain();
    }
TLJ
  • 4,525
  • 2
  • 31
  • 46
1

Inheritance of generic type parameters does not involve inheritance of the generic class. I think that generics are not the right solution here.

Try this approach:

class Magazine
{
    public readonly Type GunType; // Used for identification of compatible gun type.

    public Magazine (Type gunType)
    {
        this.GunType = gunType;
    }
}

class Gun
{
    // Factory method
    public Magazine CreateMagazine()
    {
        return new Magazine(this.GetType());
    }

    public Magazine Reload(Magazine newMag)
    {
        // Test whether the magazine is compatible with the current gun.
        if (newMag.GunType != this.GetType()) {
            throw new ArgumentException();
            // Or just reject the new magazine and keep the current one.
        }

        // Your reload logic goes here ...
    }
}

The problem with the generics is that two types T<A> and T<B> are not assignment compatible, even when there is an inheritance relationship between A and B. The other problem is, that generics are fully resolved at compile time. You need a more dynamic approach here. In a game you might want to store guns and magazines in the user's inventory and handle all the gun and magazine types the same way. Storing the required type as System.Type can be done at runtime in a fully dynamic way.

You would create and work with magazines like this:

Gun gun = new AK();
Magazine magazine = gun.CreateMagazine();
Magazine other = gun.Reload(magazine);
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188