How to bind an object to a list? - c#

This is not a windows form app, I'm not sure I'm using the correct terminology. I'm trying to bind an object to a list so when the object is modified outside of the list, those changes are reflected in the list. I'm not entirely sure how to start, my searches just keep returning "winform" answers to a datasource but this is not what I want. Here is what I have so far:
You can copy this code into a console app if you wish to test it. Notice the foreach that loops through go.Getcomponents() does not show the names because I don't think the object modified is still referenced when pulled out of the list. Essentially I'm trying to modify the object outside the list but when that object is modified the object in the list is also modified.
It is important that it can be serialized because the GameObject will be transferred across a network and data within it will be read by a server.
class Test
{
public void TestStart()
{
GameObject go = new GameObject(); //create GameObject
Dog dog = go.AddComponent<Dog>(); //Add a Dog component to the GameObject
dog.name = "Fluffy"; //name the dog fluffy, this should be reflected in the GenericComponent list of GameObject
Dog dog2 = go.AddComponent<Dog>();
dog2.name = "Fuzzy";
//loop through all dog components in GameObject go, doesn't print dog names :(
foreach (Dog dg in go.GetComponents<Dog>())
{
Console.WriteLine(dg.name);
}
Console.ReadLine();
}
}
[Serializable]
public class GameObject
{
List<GenericComponent<Object>> componentList = new List<GenericComponent<Object>>();
//returns first found in list.
public T GetComponent<T>()
{
return (T)componentList.Find(c => c.component.GetType() == typeof(T)).component;
}
//add a component to component list.
public T AddComponent<T>()
{
GenericComponent<Object> newComponent = new GenericComponent<Object>();//;(T)Activator.CreateInstance(typeof(T));
newComponent.component = (T)Activator.CreateInstance(typeof(T));
componentList.Add(newComponent);
return (T)Activator.CreateInstance(typeof(T));
}
//returns all components of type T
public List<T> GetComponents<T>()
{
List<T> r = new List<T>();
for (int i = 0; i < componentList.Count; i++)
{
if (componentList[i].component.GetType() == typeof(T))
{
r.Add((T)componentList[i].component);
}
}
return r;
}
}
[Serializable]
public class GenericComponent<T> where T : new()
{
public T component;
public GenericComponent()
{
component = new T();
}
}
[Serializable]
public class Dog
{
public string name = "";
public Dog() { }
public Dog(string name)
{
this.name = name;
}
}

In your AddComponent method, you are adding one component and then returning another one. Instead, return the same one you added:
public T AddComponent<T>()
{
GenericComponent<Object> newComponent = new GenericComponent<Object>();
newComponent.component = (T)Activator.CreateInstance(typeof(T));
componentList.Add(newComponent);
return (T)newComponent.component;
}

Related

Put Components into a list or array?

how can I collect the name of all of my Script(Generic) (in a list or array?)
Like below?
public class inputdata
{
public Component com;
public int index;
public inputdata(Component newcom,int newindex)
{
com = newcom;
index = newindex;
}
}
inputdata[] data = {new inputdata(component1, 1),inputdata(component2, 1),inputdata(component3, 1)}
foreach (inputdata ft in data)
{
movefunc <ft.com> (ft.index);
}
void movefunc <T> (int index){
gameobject.GetComponent<T>()
}
it 's shows 'component1' is a type, which is not valid in the given context.
component1 is my c# script, I use it for detecting raycast.
like this
hitInfo.collider.gameObject.GetComponent <component1 > ()
It seems not clear what you are trying to achieve. In case it helps you can achive a list of the components in a gameObject like this:
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public class GetComponets : MonoBehaviour {
public GameObject goWithComponets; //attach in the inspector
void Start() {
getComponets().ToList().ForEach(component => {
Debug.Log(component.GetType().ToString());
});
}
private IEnumerable<Component> getComponets() {
return goWithComponets.GetComponents<Component>().ToList(); ;
}
}
Your components wont be generic, they for sure will inherit from the Component type, so in case its needed you can grab all the components from the gameObject getting all the components from the base class Component, and if you need to handle specifics types, handle that later on with some code.
Sounds to me like you don't want a generic but rather something like e.g
public class inputdata
{
public Type Type;
public int Index;
public inputdata(Type type, int index)
{
Type = type;
Index = index;
}
}
inputdata[] data = new []
{
new inputdata(typeof(component1), 1),
new inputdata(typeof(component2), 1),
new inputdata(typeof(component3), 1)
};
and then
foreach (inputdata ft in data)
{
movefunc (ft.type, ft.index);
}
and
void movefunc (Type type, int index)
{
// ... whatever happens before and where you get the hitInfo from
if(hitInfo.collider.TryGetComponent(type, out var component))
{
// component of given type exists on the hit object
// "component" will contain a reference to it in the base type "Component" -> if you need it in its actual type you will need to cast
}
else
{
// there is no component of given type on the hit object
}
}

Design time serialization of custom type property

So basically I have a custom UserControl containing a private array of Label objects and I want to be able to access exclusively their Text properties from the outside.
I therefore added a property which type LabelTextCollection is an implementation of IEnumerable and has my Label array as its inner list. Furthermore, I added an implementation of UITypeEditor to allow editing from the windows forms designer.
To try it out, I added my control in a form and edited the property's value. All of that works fine until I close and reopen the designer and the labels take back their default values.
After looking around it seems I have to add an implementation of CodeDomSerializer to allow my type to succesfully serialize into the {Form}.Designer.cs file at design time. I tried serializing a comment line first to test it out but no code is generated.
My final goal would be to have a line like
this.{controlName}.Titles.FromArray(new string[] { "Whatever" } )
added at design time after the property was modified using my editor.
What am I misunderstanding and/or doing wrong ?
Custom Type
[DesignerSerializer(typeof(LabelTextCollectionSerializer), typeof(CodeDomSerializer))]
public class LabelTextCollection : IEnumerable<string>, IEnumerable
{
private Label[] labels;
public LabelTextCollection(Label[] labels)
{
this.labels = labels;
}
public void SetLabels(Label[] labels)
{
this.labels = labels;
}
public IEnumerator<string> GetEnumerator()
{
return new LabelTextEnum(labels);
}
IEnumerator IEnumerable.GetEnumerator()
{
return new LabelTextEnum(labels);
}
public string this[int index]
{
get { return labels[index].Text; }
set { labels[index].Text = value; }
}
public override string ToString()
{
if (labels.Length == 0) return string.Empty;
else
{
StringBuilder sb = new StringBuilder("{ ");
foreach (string label in this)
{
sb.Append(label);
if (label == this.Last()) sb.Append(" }");
else sb.Append(", ");
}
return sb.ToString();
}
}
public string[] ToArray()
{
string[] arr = new string[labels.Length];
for (int i = 0; i < labels.Length; i++) arr[i] = labels[i].Text;
return arr;
}
public void FromArray(string[] arr)
{
for(int i = 0; i < arr.Length; i++)
{
if (i >= labels.Length) break;
else labels[i].Text = arr[i];
}
}
public class LabelTextEnum : IEnumerator<string>, IEnumerator
{
private readonly Label[] labels;
private int position = -1;
public LabelTextEnum(Label[] labels)
{
this.labels = labels;
}
public object Current
{
get
{
try
{
return labels[position].Text;
}
catch (IndexOutOfRangeException)
{
throw new InvalidOperationException();
}
}
}
string IEnumerator<string>.Current { get { return (string)Current; } }
public void Dispose()
{
return;
}
public bool MoveNext()
{
return ++position < labels.Length;
}
public void Reset()
{
position = -1;
}
}
}
Type Editor
public class LabelTextCollectionEditor : UITypeEditor
{
IWindowsFormsEditorService _service;
IComponentChangeService _changeService;
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
_service = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));
_changeService = (IComponentChangeService)provider.GetService(typeof(IComponentChangeService));
if (_service != null && _changeService != null && value is LabelTextCollection)
{
LabelTextCollection property = (LabelTextCollection)value;
LabelTextCollectionForm form = new LabelTextCollectionForm() { Items = property.ToArray() };
if (_service.ShowDialog(form) == DialogResult.OK)
{
property.FromArray(form.Items);
value = property;
_changeService.OnComponentChanged(value, null, null, null);
}
}
}
return value;
}
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
}
Serializer
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
var baseSerializer = (CodeDomSerializer)manager.GetSerializer( typeof(LabelTextCollection).BaseType, typeof(CodeDomSerializer));
object codeObject = baseSerializer.Serialize(manager, value);
if (codeObject is CodeStatementCollection && value is LabelTextCollection)
{
var col = value as LabelTextCollection;
var statements = (CodeStatementCollection)codeObject;
statements.Add(new CodeCommentStatement("LabelTextCollection : " + col.ToString()));
}
return codeObject;
}
}
Property of custom Type
[Category("Appearance")]
[Editor(typeof(LabelTextCollectionEditor), typeof(UITypeEditor))]
public LabelTextCollection Titles { get; }
EDIT :
I added a set to my Titles property and set up my project for design-time debugging, I then realized that an exception was thrown on the line
object codeObject = baseSerializer.Serialize(manager, value);
stating that the Label type isn't marked as [Serializable].
I'm assuming that the base serializer is trying to write a call to my LabelTextCollection constructor and to serialize the labels field as a parameter of it.
I tried replacing the line with
object codeObject = new CodeObject();
which got rid of the exception but didn't write anything in the designer.cs file.
I'm (once again) assuming that nothing is happening because there is no relation between the CodeObject I just created and the file (unless that relation is established after it's returned by the Serialize method ?).
As you can probably tell, I'm pretty new regarding the CodeDom stuff so how should I create this object properly ?
EDIT 2 :
I'm so dumb... I forgot the codeObject is CodeStatementCollection test...
So the comment line is writing fine, now all I need to do is to write the correct line with CodeDom and it should work fine.
If someone wants to help, I currently have added to the designer.cs file :
this.FromArray( new string[] { "TEST" } );
So I'm missing the control's and the property's names to get to my final goal.
I'll answer my own post to recapitulate what I did to fix it when that's done.
I managed to make the serialization work as I intended so I'm going to recap what I changed from the code I originally posted.
First my property of custom type needed a set to be able to be modified by the editor.
[Editor(typeof(LabelTextCollectionEditor), typeof(UITypeEditor))]
public LabelTextCollection Titles { get; set; }
I wrongly assumed that the property's value was changing because the label's texts were effectively changing in the designer after using the editor.
That was happening because the editor could access the reference to the inner label array through the use of the LabelTextCollection.FromArray method.
With the setter, the property is now properly edited at design-time.
The rest of the changes are all in the serializer so i'm posting the whole updated code :
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeStatementCollection codeObject = new CodeStatementCollection();
if (value is LabelTextCollection)
{
LabelTextCollection col = value as LabelTextCollection;
// Building the new string[] {} statement with the labels' texts as parameters
CodeExpression[] strings = new CodeExpression[col.Count()];
for (int i = 0; i < col.Count(); i++) strings[i] = new CodePrimitiveExpression(col[i]);
CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]), strings);
// Building the call to the FromArray method of the currently serializing LabelTextCollection instance
ExpressionContext context = manager.Context.Current as ExpressionContext;
CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression, "FromArray", arrayCreation);
codeObject.Add(methodInvoke);
}
return codeObject;
}
}
To recap the changes I made in that class :
Removed the call to the baseSerializer.Serialize method to manage the whole serialization myself
Initializing the codeObject variable as a new CodeStatementCollection
Building my call to the LabelTextCollection.FromArray method using CodeDom
All of that now successfully writes the line I wanted in the Designer.cs file.
PS :
Thanks to #TnTinMn for the help and the push in the right direction.
EDIT :
After thorough testing of the serializer, I realized that the labels' texts went back to their default value when rebuilding the assembly containing the LabeltextCollection type while having a design view of a form containing my custom control opened.
The reason for that was that the property of LabeltextCollection type could not be serialized because the condition value is LabelTextCollection was false in that case as there was a discrepancy between two LabelTextCollection types from different assembly versions.
To fix that, I removed any direct reference to the type and accessed the method I needed to call through the Type class.
That got me the following serializer code :
public class LabelTextCollectionSerializer : CodeDomSerializer
{
public override object Serialize(IDesignerSerializationManager manager, object value)
{
CodeStatementCollection codeObject = new CodeStatementCollection();
// Building the new string[] {} statement with the labels' texts as parameters
string[] texts = value.GetType().GetMethod("ToArray").Invoke(value, null) as string[];
CodeExpression[] strings = new CodeExpression[texts.Length];
for (int i = 0; i < texts.Length; i++) strings[i] = new CodePrimitiveExpression(texts[i]);
CodeArrayCreateExpression arrayCreation = new CodeArrayCreateExpression(typeof(string[]), strings);
// Building the call to the FromArray method of the currently serializing LabelTextCollection instance
ExpressionContext context = manager.Context.Current as ExpressionContext;
CodeMethodInvokeExpression methodInvoke = new CodeMethodInvokeExpression(context.Expression, "FromArray", arrayCreation);
codeObject.Add(methodInvoke);
return codeObject;
}
}
You could still test the type of value using Type.Name but as my serializer only manages a single type, that wasn't needed in my case.

Why is my game serializing this class?

So I'm making a game, and it saves users' progress on the computer in a binary file. The User class stores a few things:
Integers for stat values (Serializable)
Strings for the Username and the skin assets
Lists of both the Achievement class and the InventoryItem class, which I have created myself.
Here are the User fields:
public string Username = "";
// ID is used for local identification, as usernames can be changed.
public int ID;
public int Coins = 0;
public List<Achievement> AchievementsCompleted = new List<Achievement>();
public List<InventoryItem> Inventory = new List<InventoryItem>();
public List<string> Skins = new List<string>();
public string CurrentSkinAsset { get; set; }
The Achievement class stores ints, bools, and strings, which are all serializable. The InventoryItem class stores its name (a string) and an InventoryAction, which is a delegate that is called when the item is used.
These are the Achievement class's fields:
public int ID = 0;
public string Name = "";
public bool Earned = false;
public string Description = "";
public string Image;
public AchievmentDifficulty Difficulty;
public int CoinsOnCompletion = 0;
public AchievementMethod OnCompletion;
public AchievementCriteria CompletionCriteria;
public bool Completed = false;
And here are the fields for the InventoryItem class:
InventoryAction actionWhenUsed;
public string Name;
public string AssetName;
The source of the InventoryAction variables are in my XNAGame class. What I mean by this is that the XNAGame class has a method called "UseSword()" or whatever, which it passes into the InventoryItem class. Previously, the methods were stored in the Game1 class, but the Game class, which Game1 inherits from, is not serializable, and there's no way for me to control that. This is why I have an XNAGame class.
I get an error when trying to serialize: "The 'SpriteFont' class is not marked as serializable", or something like that. Well, there is a SpriteFont object in my XNAGame class, and some quick tests showed that this is the source of the issue. Well, I have no control over whether or not the SpriteFont class is Serializable.
Why is the game doing this? Why must all the fields in the XNAGame class be serializable, when all I need is a few methods?
Keep in mind when answering that I'm 13, and may not understand all the terms you're using. If you need any code samples, I'll be glad to provide them for you. Thanks in advance!
EDIT: One solution I have thought of is to store the InventoryAction delegates in a Dictionary, except that this will be a pain and isn't very good programming practice. If this is the only way, I'll accept it, though (Honestly at this point I think this is the best solution).
EDIT 2: Here's the code for the User.Serialize method (I know what I'm doing in inefficient, and I should use a database, blah, blah, blah. I'm fine with what I'm doing now, so bear with me.):
FileStream fileStream = null;
List<User> users;
BinaryFormatter binaryFormatter = new BinaryFormatter();
try
{
if (File.Exists(FILE_PATH) && !IsFileLocked(FILE_PATH))
{
fileStream = File.Open(FILE_PATH, FileMode.Open);
users = (List<User>)binaryFormatter.Deserialize(fileStream);
}
else
{
fileStream = File.Create(FILE_PATH);
users = new List<User>();
}
for (int i = 0; i < users.Count; i++)
{
if (users[i].ID == this.ID)
{
users.Remove(users[i]);
}
}
foreach (Achievement a in AchievementsCompleted)
{
if (a.CompletionCriteria != null)
{
a.CompletionCriteria = null;
}
if (a.OnCompletion != null)
{
a.OnCompletion = null;
}
}
users.Add(this);
fileStream.Position = 0;
binaryFormatter.Serialize(fileStream, users);
You cannot serialize a SpriteFont by design, actually this is possible (.XNB file) but it hasn't been made public.
Solution:
Strip it off your serialized class.
Alternatives:
If for some reasons you must serialize some font, the first thing that comes to my mind would be to roll-out your own font system such as BMFont but that's a daunting task since you'll have to use it everywhere else where you might already do ...
Generate a pre-defined amount of fonts (i.e. Arial/Times/Courier at size 10/11/12 etc ...) using XNA Content app (can't recall its exact name); then store this user preference as two strings. With a string.Format(...) you should be able to load the right font back quite easily.
Alternative 2 is certainly the easiest and won't take more than a few minutes to roll-out.
EDIT
Basically, instead of saving a delegate I do the following:
inventory items have their own type
each type name is de/serialized accordingly
their logic does not happen in the main game class anymore
you don't have to manually match item type / action method
So while you'll end up with more classes, you have concerns separated and you can keep your main loop clean and relatively generic.
Code:
public static class Demo
{
public static void DemoCode()
{
// create new profile
var profile = new UserProfile
{
Name = "Bill",
Gold = 1000000,
Achievements = new List<Achievement>(new[]
{
Achievement.Warrior
}),
Inventory = new Inventory(new[]
{
new FireSpell()
})
};
// save it
using (var stream = File.Create("profile.bin"))
{
var formatter = new BinaryFormatter();
formatter.Serialize(stream, profile);
}
// load it
using (var stream = File.OpenRead("profile.bin"))
{
var formatter = new BinaryFormatter();
var deserialize = formatter.Deserialize(stream);
var userProfile = (UserProfile) deserialize;
// set everything on fire :)
var fireSpell = userProfile.Inventory.Items.OfType<FireSpell>().FirstOrDefault();
if (fireSpell != null) fireSpell.Execute("whatever");
}
}
}
[Serializable]
public sealed class UserProfile
{
public string Name { get; set; }
public int Gold { get; set; }
public List<Achievement> Achievements { get; set; }
public Inventory Inventory { get; set; }
}
public enum Achievement
{
Warrior
}
[Serializable]
public sealed class Inventory : ISerializable
{
public Inventory() // for serialization
{
}
public Inventory(SerializationInfo info, StreamingContext context) // for serialization
{
var value = (string) info.GetValue("Items", typeof(string));
var strings = value.Split(';');
var items = strings.Select(s =>
{
var type = Type.GetType(s);
if (type == null) throw new ArgumentNullException(nameof(type));
var instance = Activator.CreateInstance(type);
var item = instance as InventoryItem;
return item;
}).ToArray();
Items = new List<InventoryItem>(items);
}
public Inventory(IEnumerable<InventoryItem> items)
{
if (items == null) throw new ArgumentNullException(nameof(items));
Items = new List<InventoryItem>(items);
}
public List<InventoryItem> Items { get; }
#region ISerializable Members
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
var strings = Items.Select(s => s.GetType().AssemblyQualifiedName).ToArray();
var value = string.Join(";", strings);
info.AddValue("Items", value);
}
#endregion
}
public abstract class InventoryItem
{
public abstract void Execute(params object[] objects);
}
public abstract class Spell : InventoryItem
{
}
public sealed class FireSpell : Spell
{
public override void Execute(params object[] objects)
{
// using 'params object[]' a simple and generic way to pass things if any, i.e.
// var world = objects[0];
// var strength = objects[1];
// now do something with these !
}
}
Okay, so I figured it out.
The best solution was to use a Dictionary in the XNAGame class, which stores two things: an ItemType (an enumeration), and an InventoryAction. Basically, when I use an item, I check it's type and then look up it's method. Thanks to everyone who tried, and I'm sorry if the question was confusing.

Why IEnumerable<T> becomes empty after adding elements to a collection?

I have an IEnumerable<T> when I iterate through it and add it's element to a list it becomes empty?
Is there generally anything wrong about what I expect from the code?
public class Apple
{
private ICollection<Fruit> _fruits = new List<Fruit>();
public void AddFruits(IEnumerable<Fruit> fruits)
{
if (fruits == null) throw new ArgumentNullException("fruits");
foreach (var fruit in fruits)
{
_fruits.Add(fruit);
}
}
}
The caller code:
public void AddFruits(IEnumerable<Fruit> fruitsToAdd)
{
foreach (var apple in apples)
{
// Here fruitsToAdd has elements, fruitsToAdd.ToList() has two fruits.
apple.AddFruits(fruitsToAdd);
// Here fruitsToAdd has NO element!!, fruitsToAdd.ToList() is empty!
// next iteration will not add any fruit to next apple since fruitsToAdd is empty.
}
}
Update
The ToList() solved the problem. The root of the problem was that the caller to AddFruits(IEnumerable fruitsToAdd) send fruitsToAdd that was like.
fruitsToAdd = obj.Fruits.Except(apples.Fruits);
Each time IEnumerable fruitsToAdd was Rest it run above statement. Which at next iteration run Except and thereby returned no fruits.
The right way is fruitsToAdd = obj.Fruits.Except(apples.Fruits).ToList(); Since we want one evaluation.
Ok, try this:
public void AddFruits(IEnumerable<Fruit> fruitsToAdd)
{
var fruitsToAddCopy = fruitsToAdd.ToList(); // add just this line
foreach (var apple in apples)
{
apple.AddFruits(fruitsToAddCopy); // and change this
}
}
Without knowing the origin of your fruitsToAdd it's impossible to say more. Some IEnumerable<> can't be re-used. Others can.
I modified your code to get it to compile and wrote a test. Your list does not become empty after copying it's elements into the apples.
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ClassLibrary3
{
[TestClass]
public class Class1
{
[TestMethod]
public void test()
{
var fruits = new List<Fruit> {new Fruit(), new Fruit(), new Fruit()};
var lists = AddFruits(fruits);
Assert.IsTrue(fruits.Count == 3);
}
public List<Apple> AddFruits(IEnumerable<Fruit> fruitsToAdd)
{
var apples = new List<Apple>
{
new Apple(),
new Apple()
};
foreach (var apple in apples)
{
apple.AddFruits(fruitsToAdd);
}
return apples;
}
}
public class Fruit
{
}
public class Apple
{
private ICollection<Fruit> _fruits = new List<Fruit>();
public void AddFruits(IEnumerable<Fruit> fruits)
{
if (fruits == null) throw new ArgumentNullException("fruits");
foreach (var fruit in fruits)
{
_fruits.Add(fruit);
}
}
}
}
The code in your question shouldn't exhibit such behavior, so I am presuming you tried to simplify it, but removed a lot of functionality from it.
What looks a bit suspicious is that your _fruits field is of type ICollection<T>. This interface is often used with custom collection implementations. Is it possible that, in the actual code, this field isn't instantiated with a List<T>, but rather with a custom implementation of that interface?
If you have a custom collection implementation, then it is perfectly possible for its Add method to do weird stuff (like removing an item from its previous "parent" collection before adding it to its new "parent"). Tree collections often do such things to simplify moving nodes around.
[Edit]
I am aware that this is not OPs actual problem, but I will nevertheless add an example to demonstrate that a custom collection implementation can in fact modify the input collection when its members are added to a different collection.
Let's say the Fruit class looks like this:
partial class Fruit
{
private ICollection<Fruit> _children;
private Fruit _parent;
public String Name { get; set; }
public Fruit()
{
_children = new FruitCollection(this);
}
public void AddFruits(IEnumerable<Fruit> fruits)
{
foreach (Fruit f in fruits)
_children.Add(f);
}
public int NumberOfChildren
{
get { return _children.Count; }
}
public IEnumerable<Fruit> GetFruits()
{
return _children.ToList();
}
}
And there is a custom collection defined as:
partial class Fruit
{
public class FruitCollection : Collection<Fruit>
{
private readonly Fruit _parent;
public FruitCollection(Fruit parent)
{
_parent = parent;
}
protected override void InsertItem(int index, Fruit item)
{
// item already has a parent?
if (item._parent != null)
{
// remove it from previous parent
item._parent._children.Remove(item);
}
// set the new parent
item._parent = _parent;
base.InsertItem(index, item);
}
// other methods should be overriden in a similar way
}
}
Then the following program:
static void Main(string[] args)
{
List<Fruit> abc = new List<Fruit>()
{
new Fruit() { Name = "a" },
new Fruit() { Name = "b" },
new Fruit() { Name = "c" }
};
Fruit apple = new Fruit() { Name = "apple" };
apple.AddFruits(abc);
Console.WriteLine("{0} has {1} children", apple.Name, apple.NumberOfChildren);
// now try to add apples's children to
// each of the following fruits
List<Fruit> def = new List<Fruit>()
{
new Fruit() { Name = "d" },
new Fruit() { Name = "e" },
new Fruit() { Name = "f" }
};
foreach (Fruit f in def)
{
f.AddFruits(apple.GetFruits());
Console.WriteLine("{0} has {1} children", f.Name, f.NumberOfChildren);
}
Console.Read();
}
Would print:
apple has 3 children
d has 3 children
e has 0 children
f has 0 children
Because apple.GetFruits() will return 0 after the first iteration.
By looking at the custom collection's source, it is hard to realize that _children.Add(f) in AddFruits in fact modifies the fruits previous parent collection.

Saving Level in XML

Greetings to everyone. I am creating a Level with objects (Tiles, Obstacles, Character). I am experiencing a problem. The serialization is successfully made, but I get empty Lists. I want to serialize and save the attributes of each object. For example:
public class Obstacle
{
public Texture2D ob_tex;
public Rectangle ob_rec;
public bool ob_clic;
Obstacle() { } // Create Constructor
}
I use this code to save the level:
public class Level
{
public List<Obstacle> obstacles;
public LevelFile levelfile;
public Level()
{
obstacles = new List<Obstacle>();
}
public class LevelFile
{
public List<Obstacle> obstacles;
}
public void Save(String path/*, LevelFile levelfile*/)
{
levelfile = new LevelFile();
levelfile.obstacles = obstacles;
XmlSerializer serializer = new XmlSerializer(typeof(LevelFile));
using (StreamWriter streamWriter = new StreamWriter(path))
{
serializer.Serialize(streamWriter, levelfile);
}
}
}
But I get an xml file which is like this:
<LevelFile<obstacles /></LevelFile>
And nothing in it(Rectangle value, Texture and bool)....
Well, according to your code you're saving a new LevelFile() which has an obstacles member that's a list, which you set from the obstacles member in your Level() constructor, which is an empty list. Thus the XML is right, you are outputting a file with an empty list which shows as the empty tag.
So it looks like your code is correct. If you add an obstacle to your list in the constructor or save, you'll see that it's actually working correctly:
public Level()
{
obstacles = new List<Obstacle>
{
new Obstacle { /* set your properties here */ }
};
}

Categories

Resources