简体   繁体   中英

Casting in generic abstract class that implements interface

Given a base class Coin

public class Coin { }

and two derived classes Coin50Cent and Coin25Cent

public class Coin50 : Coin { }
public class Coin25 : Coin { }

The task is to create an object of a CoinMachine (used for coin exchange, eg puting 50 cent coin returns two 25 cent coins) that corresponds the following requierments:

  1. CoinMachine must have two collections of Coin25Cent and Coin50cent objects.

  2. The collections must be derived from an abstract generic class CoinStack<T> that has two methods

    void Push(T item); T Pop();

  3. CoinMachine must work as follows

     CoinMachine.Push(new Coin25()); // puts 25cent in 25c stack CoinMachine.Push(new Coin50()); // puts 50cent in 50c stack CoinMachine.Pop(); // gets 50cent from 50c stack CoinMachine.Pop(); // gets 25cent from 25c stack 

Here's my implementation Seems like I have a problem with casting in abstract CoinStack class.

namespace ConsoleApplication1
{
    /* Given condition */
    class Program
    {
        static void Main(string[] args)
        {
            CoinMachine.Push(new Coin25());
            CoinMachine.Push(new Coin50());
            CoinMachine.Pop<Coin50>();
            CoinMachine.Pop<Coin25>();
        }
    }

    public class Coin { }
    public class Coin50 : Coin { }
    public class Coin25 : Coin { }
    /* End given condition */

    public interface ICoinStack
    {
        T Pop<T>();
        void Push<T>(T item);
    }

    /* The problem within this abstract class */
    public abstract class CoinStack<T> : ICoinStack
    {
        private Queue<T> _stack = new Queue<T>();

        public T Pop<T>() { return _stack.Dequeue(); }
        public void Push<T>(T item) { _stack.Enqueue(item); }
    }

    public class CoinStack50 : CoinStack<Coin50> { }
    public class CoinStack25 : CoinStack<Coin25> { }

    public class CoinMachine
    {
        private static Dictionary<Type, ICoinStack> map;

        static CoinMachine()
        {
            map = new Dictionary<Type, ICoinStack>()
            {
                { typeof(Coin50), new CoinStack50() },
                { typeof(Coin25), new CoinStack25() }
            };
        }

        public static T Pop<T>()
        {
            var type = typeof(T);
            return map[type].Pop<T>();
        }

        public static void Push<T>(T item)
        {
            var type = typeof(T);
            map[type].Push(item);
        }
    }
}

Your problem is that ICoinStack has generic methods, like Push<T>(T item) , which basically says that an implementation of ICoinStack can accept an item of any type.

However, in your implementation CoinStack<T> , you want to limit <T> of ICoinStack.Push<T> to <T> of CoinStack<T> . The compiler should have already given you a warning, saying the type parameter T has the same name as the type parameter T of the outer type.

You have to fix your design, either by making ICoinStack itself generic (as in ICoinStack<T> ), or change it's method to accept/return object (or even better: Coin ) instead of T .

Example:

// accept/return Coin instead of T to keep ICoinStack
// and it's methods non-generic
public interface ICoinStack
{
   Coin Pop();
   void Push(Coin item);
}

// explicit interface implementation and the where T:Coin 
// constrain help us here to implement ICoinStack
public abstract class CoinStack<T> : ICoinStack where T:Coin
{
   private Queue<T> _stack = new Queue<T>();

   Coin ICoinStack.Pop() { return _stack.Dequeue(); }
   void ICoinStack.Push(Coin item) { _stack.Enqueue((T)item); }

   public T Pop() { return _stack.Dequeue(); }
   public void Push(T item) { _stack.Enqueue(item); }
}

// we need a cast in Pop<T>, and also the where T:Coin constrain
public class CoinMachine
{
   private static Dictionary<Type, ICoinStack> map;

   static CoinMachine()
   {
       map = new Dictionary<Type, ICoinStack>()
       {
           { typeof(Coin50), new CoinStack50() },
           { typeof(Coin25), new CoinStack25() }
       };
   }

   public static T Pop<T>() where T:Coin
   {
       var type = typeof(T);
       return (T)map[type].Pop();
   }

   public static void Push<T>(T item) where T:Coin
   {
       var type = typeof(T);
       map[type].Push(item);
   }
}

The solution would be to change this code:

public interface ICoinStack
{
    T Pop<T>();
    void Push<T>(T item);
}

to this:

public interface ICoinStack
{
    Coin Pop();
    void Push(Coin item);
}

and the implementation as follows:

public abstract class CoinStack<T> : ICoinStack where T: Coin
{
    private Queue<T> _stack = new Queue<T>();

    public T Pop() { return _stack.Dequeue(); }
    Coin ICoinStack.Pop() {return this.Pop(); }
    public void Push(T item) { _stack.Enqueue(item); }
    void ICoinStack.Push(Coin item) { this.Push((T) item);
}

The problem was that your code allowed to use a single CoinStack instance with different Coin implementations. When you specify generics for the entire type (the entire CoinStack<T> , you enforce the same type to be used within the class methods ( Push and Pop will only accept the same T, not any type, also notice they do not need a <T> anymore.)

Notice also the generic type constraints (see also my comment below your question). These constrains ensure that you only call Push and Pop with an instance of the Coin class (opposing to the previous code that can have any type passed trough), and thus, have improved type safety.

In your CoinMachine class, you must edit the Push and Pop methods as follows:

    // notice the generic constraint, as it is required now by the compiler
    public static T Pop<T>() where T: Coin 
    {
        var type = typeof(T);
        // we need a cast, as the `ICoinStack` now return `Coin`
        return (T) map[type].Pop();
    }

    public static void Push<T>(T item) where T: Coin
    {
        var type = typeof(T);
        map[type].Push(item);
    }

Here's how I would solve it:

public class Coin { }
public class Coin50 : Coin { }
public class Coin25 : Coin { }
/* End given condition */

public interface ICoinStack
{
    T Pop<T>() where T: Coin;
    void Push<T>(T item) where T: Coin;
}

/* The problem within this abstract class */
public abstract class CoinStack : ICoinStack
{
    private Queue<Coin> _stack = new Queue<Coin>();

    public TCoin Pop<TCoin>() where TCoin: Coin { return (TCoin)_stack.Dequeue(); }
    public void Push<TCoin>(TCoin item) where TCoin: Coin { _stack.Enqueue(item); }
}

public class CoinStack50 : CoinStack { }
public class CoinStack25 : CoinStack { }

public class CoinMachine
{
    private static Dictionary<Type, ICoinStack> map;

    static CoinMachine()
    {
        map = new Dictionary<Type, ICoinStack>()
        {
            { typeof(Coin50), new CoinStack50() },
            { typeof(Coin25), new CoinStack25() }
        };
    }

    public static T Pop<T>() where T: Coin
    {
        var type = typeof(T);
        return map[type].Pop<T>();
    }

    public static void Push<T>(T item) where T: Coin
    {
        var type = typeof(T);
        map[type].Push(item);
    }
}

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