sprocket12
sprocket12

Reputation: 5488

Is it possible to create a derived class from a base class constructor?

I have say 3 classes, Animal, Cat & Dog.

// calling code
var x = new Animal("Rex"); // would like this to return a dog type
var x = new Animal("Mittens"); // would like this to return a cat type

if(x.GetType() == typeof(Dog))
{
   x.Bark();
}
else
{
  x.Meow();
}


class Animal
{
   public Animal(string name)
   {
      // check against some list of dog names ... find rex
      // return Animal of type Dog.

      // if not...

      // check against some list of cat names ... find mittens
      // return Animal of type Cat.
   }
}

Is this possible somehow? If not is there something similar I can do?

Upvotes: 6

Views: 1481

Answers (4)

Henk Holterman
Henk Holterman

Reputation: 273449

What you are looking for is either a 'virtual constructor' (not possibe in C#) or the Factory pattern.

class Animal
{
   // Factory method
   public static Animal Create(string name)
   {
      Animal animal = null;
      ...  // some logic based on 'name'
          animal = new Zebra();

      return animal;
   }
}

The Factory method can also be placed in another (Factory) class. That gives better decoupling etc.

Upvotes: 9

Scott Chamberlain
Scott Chamberlain

Reputation: 127593

The other answers show that you need to use a factory pattern but I wanted to give you a more "practical" example of how you would do it. I did exactly what you where doing, however I was working with the EPL2 printer language. When I saw X I needed to create a instance of class Rectangle, when I saw A I needed to create a instance of class Text.

(I wrote this a long time ago so I am sure some of the things I did could be improved upon).

public partial class Epl2CommandFactory
{
    #region Singelton pattern
    private static volatile Epl2CommandFactory m_instance;
    private static object m_syncRoot = new object();

    public static Epl2CommandFactory Instance
    {
        get
        {
            if (m_instance == null)
            {
                lock (m_syncRoot)
                {
                    if (m_instance == null)
                    {
                        m_instance = new Epl2CommandFactory();
                    }
                }
            }
            return m_instance;
        }
    }
    #endregion

    #region Constructor
    private Epl2CommandFactory()
    {
        m_generalCommands = new Dictionary<string, Type>();
        Initialize();
    }
    #endregion

    #region Variables
    private Dictionary<string, Type> m_generalCommands;

    private Assembly m_asm;
    #endregion

    #region Helpers
    private void Initialize()
    {
        Assembly asm = Assembly.GetAssembly(GetType());
        Type[] allTypes = asm.GetTypes();
        foreach (Type type in allTypes)
        {
            // Only scan classes that are not abstract

            if (type.IsClass && !type.IsAbstract)
            {
                // If a class implements the IEpl2FactoryProduct interface,

                // which allows retrieval of the product class key...

                Type iEpl2FactoryProduct = type.GetInterface("IEpl2GeneralFactoryProduct");
                if (iEpl2FactoryProduct != null)
                {
                    // Create a temporary instance of that class...

                    object inst = asm.CreateInstance(type.FullName);

                    if (inst != null)
                    {
                        // And generate the product classes key

                        IEpl2GeneralFactoryProduct keyDesc = (IEpl2GeneralFactoryProduct)inst;
                        string key = keyDesc.GetFactoryKey();
                        m_generalCommands.Add(key, type);
                        inst = null;
                    }
                }
            }
        }
        m_asm = asm;
    }
    #endregion

    #region Methods
    public IEpl2Command CreateEpl2Command(string command)
    {
        if (command == null)
            throw new NullReferenceException("Invalid command supplied, must be " +
                                             "non-null.");

        Type type;
        if (!m_generalCommands.TryGetValue(command.Substring(0, 2), out type))
            m_generalCommands.TryGetValue(command.Substring(0, 1), out type);
        if (type != default(Type))
        {
            object inst = m_asm.CreateInstance(type.FullName, true,
                                               BindingFlags.CreateInstance,
                null, null, null, null);

            if (inst == null)
                throw new NullReferenceException("Null product instance.  " +
                     "Unable to create necessary product class.");

            IEpl2Command prod = (IEpl2Command)inst;
            prod.CommandString = command;
            return prod;
        }
        else
        {
            return null;
        }
    }
    #endregion
}

The way the code works is I use the singleton pattern to create a factory class so people can call var command = Epl2CommandFactory.Instance.CreateEpl2Command("..."); passing in the EPL2 command string and it returns a instance of the class that represents that specific class.

During initialization I use reflection to find classes that support the IEpl2GeneralFactoryProduct interface, if the class supports the interface the factory stores the one or two letter code representing the printer command in a dictionary of types.

When you try to create the command the factory looks up the printer command in the dictionary and creates the correct class, it then passes the full command string on to that class for further processing.

Here is a copy of a command class and it's parents if you wanted to see it

Rectangle:

[XmlInclude(typeof(Rectangle))]
public abstract partial class Epl2CommandBase { }

/// <summary>
/// Use this command to draw a box shape.
/// </summary>
public class Rectangle : DrawableItemBase, IEpl2GeneralFactoryProduct
{
    #region Constructors
    public Rectangle() : base() { }
    public Rectangle(Point startingLocation, int horozontalEndPosition, int verticalEndPosition)
        : base(startingLocation)
    {
        HorizontalEndPosition = horozontalEndPosition;
        VerticalEndPosition = verticalEndPosition;
    }
    public Rectangle(int x, int y, int lineThickness, int horozontalEndPosition, int verticalEndPosition)
        : base(x, y)
    {
        LineThickness = lineThickness;
        HorizontalEndPosition = horozontalEndPosition;
        VerticalEndPosition = verticalEndPosition;
    }
    #endregion

    #region Properties
    [XmlIgnore]
    public int LineThickness { get; set; }
    [XmlIgnore]
    public int HorizontalEndPosition {get; set;}
    [XmlIgnore]
    public int VerticalEndPosition { get; set; }

    public override string CommandString
    {
        get
        {
            return String.Format("X{0},{1},{2},{3},{4}", X, Y, LineThickness, HorizontalEndPosition, VerticalEndPosition);
        }
        set
        {
            GenerateCommandFromText(value);
        }
    }
    #endregion

    #region Helpers
    private void GenerateCommandFromText(string command)
    {
        if (!command.StartsWith(GetFactoryKey()))
            throw new ArgumentException("Command must begin with " + GetFactoryKey());
        string[] commands = command.Substring(1).Split(',');
        this.X = int.Parse(commands[0]);
        this.Y = int.Parse(commands[1]);
        this.LineThickness = int.Parse(commands[2]);
        this.HorizontalEndPosition = int.Parse(commands[3]);
        this.VerticalEndPosition = int.Parse(commands[4]);

    }
    #endregion

    #region Members
    public override void Paint(Graphics g, Image buffer)
    {
        using (Pen p = new Pen(Color.Black, LineThickness))
        {
            g.DrawRectangle(p, new System.Drawing.Rectangle(X, Y, HorizontalEndPosition - X, VerticalEndPosition - Y));
        }

    }

    public string GetFactoryKey()
    {
        return "X";
    }
    #endregion
}

DrawableItemBase:

public abstract class DrawableItemBase : Epl2CommandBase, IDrawableCommand
{
    protected DrawableItemBase()
    {
        Location = new Point();
    }
    protected DrawableItemBase(Point location)
    {
        Location = location;
    }
    protected DrawableItemBase(int x, int y)
    {
        Location = new Point();
        X = x;
        Y = y;
    }
    private Point _Location;
    [XmlIgnore]
    public virtual Point Location
    {
        get { return _Location; }
        set { _Location = value; }
    }

    [XmlIgnore]
    public int X
    {
        get { return _Location.X; }
        set { _Location.X = value; }
    }
    [XmlIgnore]
    public int Y
    {
        get { return _Location.Y; }
        set { _Location.Y = value; }
    }

    abstract public void Paint(Graphics g, Image buffer);
}

Epl2CommandBase:

public abstract partial class Epl2CommandBase : IEpl2Command
{
    protected Epl2CommandBase() { }

    public virtual byte[] GenerateByteCommand()
    {
        return Encoding.ASCII.GetBytes(CommandString + '\n');
    }
    public abstract string CommandString { get; set; }
}

Various Interfaces:

public interface IEpl2GeneralFactoryProduct
{
    string GetFactoryKey();
}
public interface IEpl2Command
{
    string CommandString { get; set; }
}

public interface IDrawableCommand : IEpl2Command
{
    void Paint(System.Drawing.Graphics g, System.Drawing.Image buffer);
}

Upvotes: 0

Joseph Devlin
Joseph Devlin

Reputation: 1800

No I dont think it is possible in the way that you want.

You could create a static class that has a method that returns an animal based on a name e.g.

static Animal CreateAnimal(string name)
{
    if(catList.Contains(name))
        return new Cat(name");
    else if(dogList.Contains(name))
        return new Dog(name);

    return null;
}

Upvotes: 1

Jon Skeet
Jon Skeet

Reputation: 1502036

No. Basically the right fix is to use a static method which can create an instance of the right type:

var x = Animal.ForName("Rex");
var x = Animal.ForName("Mittens");

...

public abstract class Animal
{
    public static Animal ForName(string name)
    {
        if (dogNames.Contains(name))
        {
            return new Dog(name);
        }
        else
        {
            return new Cat(name);
        }
    }
}

Or this could be an instance method in an AnimalFactory type (or whatever). That would be a more extensible approach - the factory could implement an interface, for example, and could be injected into the class which needed to create the instances. It really depends on the context though - sometimes that approach is overkill.

Basically, a new Foo(...) call always creates an instance of exactly Foo. Whereas a static method declared with a return type of Foo can return a reference to any type which is compatible with Foo.

Upvotes: 5

Related Questions