简体   繁体   中英

How do i convert a class (which is derived from a generic "base" class) to that generic "base" class

I created a base class ("Element") and a base list class ("Elements") as generic class. The generic list class should only be able to contain classes, which are of Type "Element" of derived from "Element". The "Element" class should own a "ParentRoot" property, which should contain the base list class ("Elements")!

public class Element
{
    public Elements<Element> ParentRoot { get; set; }
}

public class Elements<T> : List<T> where T : Element
{
}

Now i create two classes and two list classes which are derived form the classes above. But i'm failing on setting the "ParentRoot" property:

public class Ceiling : Element
{
    public Ceiling(Ceilings parent)
    {
        Parent = parent;
        ParentRoot = parent;
    }

    public Ceilings Parent { get; set; }
}

public class Ceilings : Elements<Ceiling>
{
}

public class Wall : Element
{
    public Wall(Walls parent)
    {
        Parent = parent;
        ParentRoot = parent;
    }

    public Walls Parent { get; set; }
}

public class Walls : Elements<Wall>
{
}

I get two errors at:

ParentRoot = parent;

Cannot implicitly convert type "Ceilings" to "Elements" Cannot implicitly convert type "Walls" to "Elements"

Is there a solution for this problem?

Thanks for any help!

EDIT:

OK, i have to be a bit more specific. I expanded the code a bit:

public class Room
{
    public Room(Rooms parent)
    {
        Parent = parent;
    }

    public Rooms Parent { get; set; }
}

public class Rooms : List<Room>
{

}

public class Element
{
    public Elements<Element> ParentRoot { get; set; }

    public Rooms FindRoomsToElement()
    {
        Rooms rooms = new Rooms();

        foreach (Room room in ParentRoot.Parent.Parent)
        {
            // Do stuff here

            // if i rename the "ParentRoot" property to "Parent" and make it "virtual",
            // and the other properties overwrite it with the "new" key, then this will
            // get a null exception!
            // i haven't testet it, but i think abstrakt will bring the same/similar result

            // if i make the "ParentRoot" property IEnumerable, then there will no
            // ParentRoot.Parent be available
        }

        return rooms;
    }
}

public class Elements<T> : List<T> where T : Element
{
    public Elements(Room parent)
    {
        Parent = parent;
    }

    public Room Parent { get; set; }
}



public class Ceiling : Element
{
    public Ceiling(Ceilings parent)
    {
        Parent = parent;
        //ParentRoot = parent;
    }

    public Ceilings Parent { get; set; }
}

public class Ceilings : Elements<Ceiling>
{
    public Ceilings(Room parent) : base(parent)
    {
    }
}

public class Wall : Element
{
    public Wall(Walls parent)
    {
        Parent = parent;
        //ParentRoot = parent;
    }

    public Walls Parent { get; set; }
}

public class Walls : Elements<Wall>
{
    public Walls(Room parent) : base(parent)
    {
    }
}

I hope this makes it more precise.

You aren't allowed to do this because if you could, you could put the wrong kind of elements into a List .

 Elements<Ceilings> ceilings = someCeilings;
 Elements<Element> parentRoot = ceilings; // Not allowed; imagine it is though.
 Wall wall = someWall;
 parentRoot.Add(wall); // Oops - we just added a wall to someCeilings!

If you can just treat the walls and/or ceilings as a sequence, you can do use IEnumerable<Element> instead (which works because IEnumerable<T> is "covariant"):

IEnumerable<Element> parentRoot = ceilings; // OK

This is OK because IEnumerable<Element> has no way to modify the original collection.

The problem is that, given a Generic<T> and a Child : Base , the type Generic<Base> is not a base of Generic<Child> . Generics are not base classes for their concrete implementations - they are templates out of which concrete implementations can be created and, in turn, the concrete implementations don't have a hierarchical relationship with each other . Consider the following snippet to understand why this is so:

var bananas = List<Banana>();
var fruits = (List<Fruit>)bananas; // If this was valid
fruits.Add(new Orange()); // Then this would be valid
// So we'd have an Orange to a list of runtime type List<Banana>

Therefore, your Elements<Element> , which is a case of the Generic<Base> I described above, cannot work as a base for the others. Your Ceilings and Walls are neither implicitly nor explicitly convertible to Elements<Element> .

A possible workaround would be to make ParentRoot a virtual or better yet an abstract property (if Element can be abstract) and override it in every subclass of Element to manually convert the Parent property to the Elements<Element> type.

For example, you could change your base and your generic like this:

public abstract class Element
{
    public abstract Elements<Element> ParentRoot { get; }
}

public class Elements<T> : List<T> where T : Element
{
    public Elements<T>() : base()
    {
    }
    public Elements<T>(ICollection<T> collection) : base(collection)
    {
    }
}

Then, for every subclass, do the following:

public class Wall : Element
{
    public Wall(Walls parent)
    {
        Parent = parent;
    }

    public Walls Parent { get; set; }
    public override Elements<Element> ParentRoot
    {
        get
        {
            return new Elements<Element>(Parent);
        }
    }
}

Of course, modifications to the object returned by ParentRoot will not affect Parent . But this is okay semantically, because (as I described with the bananas and the oranges), you wouldn't want to accidentally add a Ceiling to a Walls just because it looks like an Elements<Element> at some point in the code.

Instead of this:

Parent = parent;
ParentRoot = parent;

try this

Parent = parent;
ParentRoot = new Elements<Element>();
ParentRoot.AddRange(parent);

My answer is based on seeing your code and think that you are trying to build a room with n elements.Using composition "has-a" or "is-part-of" and a freely take on factory pattern i think you can achieve this.In my code i based in a "room" "has" elements,if you think "elements" "is-a" "room"?...so elements is part of the room,and those elements in your case are ceiling and wall,now wall "is-a" element and ceiling "is-a" element of room,then naturaly i derived those from element but keeping a "reference" in room for element.About having a list of rooms i nested a private class,since there is no need(in my opinion) for wall or ceiling to have access to available rooms so in room class you do all the work.In room class i derived interface IRoomBuilder with proper methods,the ones uncommented are the ones you should use for creating for example a wall with some specifications and add to room,the commented ones are just for example purposes.I placed some user-side code for you to test.

    public interface IRooms
    {
        List<Room> AvailableRooms();
    }

    public interface IRoomBuilder
    {
        //void MakeWall();
        //void MakeWalls(int NumWalls);
        //void MakeCeiling();
        //void MakeCeilings(int NumCeilings);
        void MakeElement(Element el);
        void MakeElements(List<Element> elmts);
    }

    public class Room:IRoomBuilder
    {
        private List<Element> roomelements;
        private readonly Rooms ShowRooms;

        public List<Element> RoomElements
        {
            get { return roomelements; }
            set { RoomElements.AddRange(value); }
        }

        public Room()
        {
            roomelements = new List<Element>();
            ShowRooms = new Rooms();
        }

        public void MakeElement(Element el)
        {
            RoomElements.Add(el);
        }

        public void MakeElements(List<Element> elmts)
        {
            RoomElements.AddRange(elmts);
        }

        //public void MakeWall()
        //{

        //    RoomElements.Add(Element.MakeElement(typeof(Wall).Name));
        //}

        //public void MakeWalls(int NumWalls)
        //{
        //    for (int i = 0; i < NumWalls; i++)
        //    {
        //        RoomElements.Add(Element.MakeElement(typeof(Wall).Name));
        //    }
        //}

        //public void MakeCeiling()
        //{
        //    RoomElements.Add(Element.MakeElement(typeof(Ceiling).Name));
        //}

        //public void MakeCeilings(int NumCeilings)
        //{
        //    for (int i = 0; i < NumCeilings; i++)
        //    {
        //        RoomElements.Add(Element.MakeElement(typeof(Ceiling).Name));
        //    };
        //}

        public void AddRoom()
        {
            ShowRooms.Add(this);
        }

        public List<Room> GetAllRooms()
        {
            IRooms r = (IRooms)ShowRooms;
            return r.AvailableRooms();
        }

        public override string ToString()
        {
            return "I am a room with " + RoomElements.Count.ToString() + " Elements";
        }

        private class Rooms : List<Room>,IRooms
        {  
            List<Room> IRooms.AvailableRooms()
            {
                return this;
            }
        }
    }

    public abstract class Element
    {
        //this method is used for the commented methods
        public static Element MakeElement(string name)
        {
            if (name == typeof(Ceiling).Name)
                return new Ceiling() as Element;
            else if (name == typeof(Wall).Name)
                return new Wall() as Element;
            else
                throw new ArgumentException("Parameter not valid");
        }
    }

    public class Ceiling : Element
    {
        //your implementation.

        public override string ToString()
        {
            return "I am a ceiling";
        }
    }

    public class Wall : Element
    {
        //your implementation.

        public override string ToString()
        {
            return "I am a wall!";
        }
    }

Client-side code example:

            Wall w = new Wall();
            Ceiling c = new Ceiling();
            Room r = new Room();
            r.MakeElement(w);
            r.MakeElement(c);
            List<Element> NewElements = new List<Element>{ new Wall(), new Ceiling() };
            r.MakeElements(NewElements);
            //r.MakeWalls(5);
            //r.MakeCeilings(6);
            r.AddRoom();
            foreach (Room room in r.GetAllRooms())
            {
                MessageBox.Show(room.ToString());
                foreach (Element el in room.RoomElements)
                {
                    MessageBox.Show(el.ToString());
                }
            }

Hope this helps.

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