Okay, I'm a little fuzzy on how this works or if it's possible. I want to serialize the Child class, but I don't actually want to serialize a Parent object when it does the Child.MyParent field... I just want the reference to be serialized. Is this possible and how would I go about it?
public class Parent
{
public Child New()
{
return new Child(this);
}
}
public class Child
{
public Parent MyParent;
public Child(Parent parent)
{
MyParent = parent;
}
}
Edit: I'm using DataContractSerializer, but I'm not opposed to switching to something else if necessary.
The XMLIgnoreAttribute can be applied to fields that you don's want serialized. For example,
public class Child
{
[XmlIgnore]
public Parent MyParent;
public Child(Parent parent)
{
MyParent = parent;
}
}
But as far as serializing a reference to the field, you'd have to provide more info on how you plan to persist the object that the reference points to. What is your reason to not just serialize the Parent member (in your case)? It's common to serialize all the public members that are needed.
If you just want to use serialization to clone, something like this should work:
private static Parent Clone(Parent parent)
{
Parent parentClone = null;
lock (m_lock) // serialize cloning.
{
IFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, parent);
stream.Seek(0, SeekOrigin.Begin);
parentClone = (Parent)formatter.Deserialize(stream);
}
}
return parentClone;
}
Sounds like you might need to implement your own serialization and deserialization functionality.
http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.getobjectdata.aspx
Here's an extract from MSDN
[Serializable]
public class Person : ISerializable
{
private string name_value;
private int ID_value;
public Person() { }
protected Person(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new System.ArgumentNullException("info");
name_value = (string)info.GetValue("AltName", typeof(string));
ID_value = (int)info.GetValue("AltID", typeof(int));
}
[SecurityPermission(SecurityAction.LinkDemand,Flags = SecurityPermissionFlag.SerializationFormatter)]
public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
throw new System.ArgumentNullException("info");
info.AddValue("AltName", "XXX");
info.AddValue("AltID", 9999);
}
public string Name
{
get { return name_value; }
set { name_value = value; }
}
public int IdNumber
{
get { return ID_value; }
set { ID_value = value; }
}
}
Related
The above question is very vague so let me elaborate.
In my code I have it set up something like this:
[ProtoContract]
[ProtoInclude(50, typeof(SubGroup))]
public class BaseGroup
{
[ProtMember(1)]
List<BaseElement> elements;
}
[ProtoContract]
public class SubGroup : BaseGroup
{
//Some protomembers
}
[ProtoContract]
[ProtoInclude(100, typeof(Set))]
public class BaseElement
{
[ProtoMember(1, AsReference = true)]
BaseGroup Parent;
}
[ProtoContract]
public class Set : BaseElement
{
//some protomembers here
[ProtoMember(1)]
List<Band> bands;
}
[ProtoContract]
public class Band
{
//some protomembers here
[ProtoMember(1, AsReference = true)]
Set Parent;
}
Now, somewhere in another part of my code I perform something like this:
public void Function(Band b)
{
Set parentSet = b.Parent;
SubGroup parentGroup = (SubGroup)parentSet.Parent;
foreach(Set s in parentGroup.elements)
{
if(!s.Equals(parentSet))
{
//This section of code is skipped when references s and parentSet are equal.
//I then save to file by serializing the entire Basegroup, I
//then deserialize back into a BaseGroup object.
//Once deserialized, this function is called and this part of the code
//is executed meaning the objects with supposedly the same reference
//are not equal anymore.
//I performed this test with only one Set object meaning only one object in
//in the List of elements in the BaseGroup object
}
}
}
I hope I explained this correctly. I have only been doing C# for about a year.
Serialization/deserialization does not preserve object references. Thus, after deserialization every object is new.
Ok, so I didn't quite fully understand why I was getting the results I got. I did however discover a method in working around this problem. I decided to remove the [Protomember(n,AsReference = true)] attribute from all the parent references. I then constructed a function with the [ProtoAfterDeserializationAttribute]attribute that will go through each object and assign each Parent reference using this keyword. This will ensure the objects are of equal reference.
I realise thread is old now but the follow code works for me. AsReference = true indicates its stored as a reference and as you can see below each "Parent" object is stored as a single object, rather than new instances. See the debugger watch window.
[ProtoContract]
public class Parent
{
[ProtoMember(1)]
public List<Child> Children
{
get { return m_Children; }
set { m_Children = value; }
}
private List<Child> m_Children = new List<Child>();
}
[ProtoContract]
public class Child
{
[ProtoMember(1, AsReference = true)]
public Parent Parent
{
get { return m_Parent; }
set { m_Parent = value; }
}
[ProtoMember(2)]
public int Index
{
get { return m_Index; }
set { m_Index = value; }
}
Parent m_Parent;
int m_Index;
}
//Test Code
Parent p = new Parent();
p.Children.Add(new Child()
{
Parent = p,
Index = 0
});
p.Children.Add(new Child()
{
Parent = p,
Index = 1
});
using (var file = File.Create("graph.bin"))
{
Serializer.Serialize(file, p);
}
Parent newPerson;
using (var file = File.OpenRead("graph.bin"))
{
newPerson = Serializer.Deserialize<Parent>(file);
}
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?
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.
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.
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;
}
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);
}
Thanks for taking a look!
I'm working on a new version of a product that is deployed in the field. I need to maintain the ability to deserialize exiting files from the older software.
Here is a contrived example:
I have a existing customer base with serialized files that they need to access. For the purposes of this question, they have a "Zoo" file with a List of Giraffes in it.
[Serializable]
public class Giraffe
: ISerializable
{
public int Age { get; private set; }
public Giraffe(int age)
{
Age = age;
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Age", Age);
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Giraffe(SerializationInfo info, StreamingContext context)
{
Age = info.GetInt32("Age");
}
}
Now, we are deploying a new version of our "Zoo" software, and we are going to support anaimals other than Giraffes, we should have done this to begin with, but due to time constrains, we had to release 1.0 with a Giraffe-only feature set.
public interface IAnimal
{
int Age { get; }
}
[Serializable]
public class Animal
: IAnimal,
ISerializable
{
public int Age { get; private set; }
public Animal (int age)
{
Age = age;
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Age", Age);
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Animal(SerializationInfo info, StreamingContext context)
{
Age = info.GetInt32("Age");
}
}
I'm using a custom serializationBinder to have old Giraffes deserialized as Animals
public class LegacyZooSerializationBinder
: SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.StartsWith("Test.Giraffe"))
return typeof(Animal);
else if (typeName.StartsWith("System.Collections.Generic.List`1[[Test.Giraffe"))
return typeof(List<Animal>);
else return null;
}
}
The problem is that I wan't to have my Zoo class use a List as it's storage, not a List. I want to do this for two reasons, for future extendability, also, so that I can more easily mock things out for unit testing.
Deserializing the new IAnimal list is no problem. The problem comes when I want to deserialize the old style items. The Binder returns the correct type, the correct deserialization constructor is called, everything looks ok, but the List is actually full of null items. Once the IDeserializationCallback.OnDeserialization callback is called, the list is correct. I can't simply call IEnumerable.ConvertAll<>() on it, because it looks like the serialization framework is trying to find the exact same instance when it comes back to clean everything up, and ConvertAll will create a new list.
I have it working as of now, but I hope someone out there can help me clean this up, as it is not all that maintainable as of now. Here is what it takes to do it:
[Serializable]
public class Zoo
: ISerializable,
IDeserializationCallback
{
List<IAnimal> m_List = null;
List<Giraffe> m_LegacyList = null; //Just so that we can save an old-style zoo
//Temp copy of the list
List<Animal> m_List_Deserialization_Temp_Copy = null;
public Zoo(bool legacy)
{
m_List = new List<IAnimal>();
if (legacy)
{
//Create an old style zoo, just for the example
m_LegacyList = new List<Giraffe>();
m_LegacyList.Add(new Giraffe(0));
m_LegacyList.Add(new Giraffe(1));
}
else
{
m_List.Add(new Animal(0));
m_List.Add(new Animal(1));
}
}
public List<IAnimal> List
{
get { return m_List; }
}
void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
{
if(m_LegacyList != null) //Save as an old style list if we have old data
info.AddValue("list", m_LegacyList);
else
info.AddValue("list", m_List);
}
[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
private Zoo(SerializationInfo info, StreamingContext context)
{
try
{
//New style
m_List = (List<IAnimal>)info.GetValue("list", typeof(List<IAnimal>));
}
catch (InvalidCastException)
{
//Old style
//Put it in a temp list, until the OnDeserialization callback is called, this will be a list, full of null items!
m_List_Deserialization_Temp_Copy = (List<Animal>)info.GetValue("list", typeof(List<Animal>));
}
}
void IDeserializationCallback.OnDeserialization(object sender)
{
if (m_List_Deserialization_Temp_Copy != null)
{
m_List = new List<IAnimal>();
//This works because IEnumerable<Animal> is covariant to IEnumerable<IAnimal>
m_List.AddRange(m_List_Deserialization_Temp_Copy);
}
}
}
Here is a basic test console app that shows serialization and deserialization for both cases:
static void Main(string[] args)
{
{
var item = new Zoo(false);
var formatter = new BinaryFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, item);
stream.Position = 0;
formatter.Binder = new LegacyZooSerializationBinder();
var deserialized = (Zoo)formatter.Deserialize(stream);
Debug.Assert(deserialized.List.Count == 2);
Debug.Assert(deserialized.List[0] != null);
Debug.Assert(deserialized.List[0].Age == 0);
Debug.Assert(deserialized.List[1] != null);
Debug.Assert(deserialized.List[1].Age == 1);
Console.WriteLine("New-style Zoo serialization OK.");
}
{
var item = new Zoo(true);
var formatter = new BinaryFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, item);
stream.Position = 0;
formatter.Binder = new LegacyZooSerializationBinder();
var deserialized = (Zoo)formatter.Deserialize(stream);
Debug.Assert(deserialized.List.Count == 2);
Debug.Assert(deserialized.List[0] != null);
Debug.Assert(deserialized.List[0].Age == 0);
Debug.Assert(deserialized.List[1] != null);
Debug.Assert(deserialized.List[1].Age == 1);
Console.WriteLine("Old-style Zoo serialization OK.");
}
Console.ReadKey();
}
Any suggestions would be greatly appreciated. I'm having a hard time finding good resources on this type of thing. Thanks!
Consider doing a one time conversion from the old files to the new format, preferably at install time and definitely after backing them up. That way you dont have to support this weird one-off serialization forever, and your codebase becomes drastically simpler.
I need to make all my entities serializable. So I was thinking in a BaseEntity with a Backup and a Restore method. But in the restore I can't override the object with the saved one because this is read-only.
Any solution or some other way to get the serializable entities?
My code:
internal class BaseEntity
{
private MemoryStream ms = new MemoryStream();
private BinaryFormatter bf = new BinaryFormatter();
public void Backup()
{
bf.Serialize(ms, this);
}
public void Restore()
{
this = (BaseEntity)bf.Deserialize(ms);
}
}
The more common pattern is to not make it the responsibility of your objects to serialize/deserialize themselves; rather, use an external serializer:
var serializer = new DataContractJsonSerializer(typeof(YourClass));
var stream = ...;
YourClass yourObj = ...;
serializer.WriteObject(stream, yourObj);
var restoredObj = serializer.ReadObject(stream);
Edit: One way serialization can work is to use the System.Runtime.Serialization.Formatters.Binary.BinaryFormatter (or other implementation of IFormatter). To serialize an object you pass the object and a stream. To Deserialize the object, you pass a stream (positioned at the begining of your serialized data), and it returns the serialized object and all its depenedencies.
public static class EntityBackupServices
{
public static MemoryStream Backup (BaseEntity entity)
{
var ms = new MemoryStream();
Serialize (ms, entity);
ms.Position = 0;
return ms;
}
public static void Serialize (Stream stream, BaseEntity entity)
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize (stream, entity);
}
public static BaseEntity Restore (Stream stream)
{
var binaryFormatter = new BinaryFormatter();
var entity = (BaseEntity) binaryFormatter.Deserialize (stream);
return entity;
}
}
One thing a formatter don't do (though the FormatterServices class makes it possible) is modify existing objects. So you probably don't want to have an instance method called Deserialize. You can't really do this: new LionEntity().Deserialize () where it replaces the fields of an existing instance.
Note: You'll need to put Serializable over all your types. Any fields that can't be serialized (because it's either not a struct, or it's not marked as [Serializable] will need to be marked with NonSerialized.
// A test object that needs to be serialized.
[Serializable()]
public class BaseEntity
{
public int member1;
public string member2;
public string member3;
public double member4;
// A field that is not serialized.
[NonSerialized()] public MyRuntimeType memberThatIsNotSerializable;
public TestSimpleObject()
{
member1 = 11;
member2 = "hello";
member3 = "hello";
member4 = 3.14159265;
memberThatIsNotSerializable = new Form ();
}
public MemoryStream Backup ()
{
return EntityBackupServices.Backup (this);
}
}
Edit:
The way I've mentioned is a rather standard and accepted way. If you want to venture into hackdom, you can deserialize the object the way I've mentioned, then use reflection to set each field on your existing object to the value of the deserialized object.
public class BaseEntity
{
void Restore(Stream stream)
{
object deserialized = EntityBackupServices.RestoreDeserialize(stream);//As listed above
if (deserialized.GetType () != this.GetType ())
throw new Exception();
foreach (FieldInfo fi in GetType().GetFields())
{
fi.SetValue(this, fi.GetValue (deserialized));
}
}
}
public IEntidadBase Restore()
{
return (IEntidadBase)bf.Deserialize(ms);
}
#jacklondon how would you do EntitySerializer methods?
You can do serialization process with http://www.servicestack.net/ StackService.Text module for clean entities. You don't need any attribute (serializable/datacontract) in ms way.
public class EntityFoo
{
public string Bar { get; set; }
public EntityFoo (string bar)
{
Bar = bar;
}
}
public class EntityDumper //and the EntitySerializer
{
public static string Dump<T> (T entity)
{
return new TypeSerializer<T> ().SerializeToString (entity);
}
public static T LoadBack<T> (string dump)
{
return new TypeSerializer<T> ().DeserializeFromString (dump);
}
}
public class dump_usage
{
public void start ()
{
string dump = EntityDumper.Dump (new EntityFoo ("Space"));
EntityFoo loaded = EntityDumper.LoadBack<EntityFoo> (dump);
Debug.Assert (loaded.Bar == "Space");
}
}
I don't necessarily recommend this, but here is one pattern for an object that can persist and restore its own state using serialization that creates new instances:
public sealed class MyClass
{
private Data _data = new Data();
//Properties go here (access the public fields on _data)
public void Backup()
{
//Serialize Data
}
public void Restore()
{
//Deserialize Data and set new instance
}
private sealed class Data
{
//Public fields go here (they're private externally [because Data is private], but public to MyClass.)
}
}
Note that this only works if your serializer supports non-public classes. Worst-case, you have to make the nested class public, which is ugly, but doesn't hurt encapsulation (since the instance is private).
language specific (however, if you need a language please use C++/C# or Javascript). I trying to figure out how I would go about doing this, and how I would access the child objects from a parent object.
Say I have the following classes, and these are not written properly etc... :
Class: roomContainer (container of objects)
Class: Table (base class for a table, contains property of maximum-seats, current-number-of-seats, array of seats )
Class: Desk (extends Table, contains property for maximum draws, array of draws )
Class: seat (base class for seats, contains property of maximum-legs, arm-rest, back-rest)
Class: couch (extends seat, adds property maximum seats)
If I create an instance of roomContainer, and add within it's container a table, couch. Within the table I create multiple seats (or chairs), and a desk.
How would I be able to access the property of child objects property, when the parent has a container of different objects. I.e the roomContainer Container of objects, one of them is a table, and a desk - where the desk has different properties and an array of draws etc.. ?
You're looking for something called the Composite Design Pattern. This allows you to nest objects (as you described), and hold references to both the parent and the children (though some implementations do no maintain a parent reference - this is optional).
Here is an example implementation using your schema:
public static class Program // the supporting class definitions are below
{
public static void Main()
{
// create a root container
var room = new RoomContainer();
// create a child
var table = new Table(room, 4);
// put the table in the room
room.Add(table);
MakeMess(room);
}
// to show you how to access the properties
// if you don't already have a reference:
public static void MakeMess(RoomContainer room)
{
if(room == null)
{
throw new ArgumentNullException("room");
}
var seats = room.GetChildren<Table>().First().Seats.ToArray();
for (int index = 0; index < seats.Length; index++)
{
Console.WriteLine("You have kicked over Seat #{0}",(index+1).ToString());
}
}
}
// This is the base class of the components and provides the core functionality.
// You will want to make this object's interface minimal, so that the logic
// is consistent with all its children (without knowing what they might be in advance)
public abstract class Component
{
private readonly IList<Component> _children;
private readonly Component _container;
protected Component(Component container)
{
_container = container;
_children = new Component[] { };
}
public bool IsRoot { get { return _container == null; } }
public abstract bool IsContainer { get; }
public virtual void Add(Component component)
{
if (component == null)
{
throw new ArgumentNullException("component");
}
if (!IsContainer)
{
throw new NotSupportedException("Add is not supported by leaf components");
}
_children.Add(component);
}
public IEnumerable<T> GetChildren<T>()
where T: Component
{
if (!IsContainer)
{
throw new NotSupportedException("Only containers have children");
}
return _children.OfType<T>();
}
public IEnumerable<Component> Children
{
get
{
if (!IsContainer)
{
throw new NotSupportedException("Only containers have children");
}
return _children;
}
}
}
public class RoomContainer : Component
{
public RoomContainer() : base(null)
{
}
public override bool IsContainer { get { return true; } }
}
public class Table : Component
{
private readonly int _maximumSeatCount;
public Table(Component container, int maximumSeatCount) : base(container)
{
_maximumSeatCount = maximumSeatCount;
}
public override bool IsContainer { get { return true; } }
protected virtual bool CanAdd(Component component)
{
return component is Seat && MaximumSeatCount > CurrentSeatCount;
}
public override void Add(Component component){
if(CanAdd(component)){
base.Add(component);
}
else
{
throw new NotSupportedException("The component was an invalid child of Table and could not be added.");
}
}
public int MaximumSeatCount { get { return _maximumSeatCount; } }
public int CurrentSeatCount { get { return Seats.Count(); } }
public IEnumerable<Seat> Seats { get { return Children.OfType<Seat>(); } }
}
public class Seat : Component
{
// you can restrict the constructor to only accept a valid parent
public Seat(Table table) : base(table)
{
}
public override bool IsContainer
{
get { return false; }
}
}
If the all share common methods, for example Render(), Update(), SaveDetails(int Id), LoadDetails(int Id) then you could make them all inherit from a base class, or all impliment a common interface. This would remove the need for casting (below) when calling a common method (or accessing a common property).
To access properties unique to the derived class you would check the type of the child object, then cast the child object to access the property.
EDIT: Example:
foreach(Object obj in Room.ChildObjects)
{
if(obj is Desk)
{
Desk DeskObj = obj as Desk; // Cast the object reference as a desk.
DeskObj.MaxDraws = 50; // It's a big desk!
DestObj.Draws[1] = new Draw(); // ......
}
}
Something like this:
IEnumerable<Desk> desks = roomContainer.OfType<Desk>();
//Iterate and do stuff.
IEnumerable<Table> tables = roomContainer.OfType<Table>();
//Iterate and do stuff.