How to conduct proper deep copying? - c#

I'm trying to perform a deep copy of an object in C# so when I do the following:
Route currentBestRoute = Ants[0].Route;
currentBestRoute would not change after altering Ants[0].Route.
I have tried altering the Route class:
using System;
using System.Collections.Generic;
namespace ACO.Models
{
public class Route : ICloneable
{
public List<City> Cities = new List<City>();
public string Name
{
get
{
string name = "";
for(int i = 0; i < Cities.Count; i++)
{
name += Cities[i].Name;
if (i != Cities.Count - 1)
{
name += "->";
}
}
return name;
}
}
public double Distance
{
get
{
double distance = 0.0;
for(int i = 0; i < Cities.Count - 1; i++)
{
distance += Cities[i].measureDistance(Cities[i + 1]);
}
return distance;
}
}
public object Clone()
{
Route route = new Route
{
Cities = Cities
};
return route;
}
}
}
and conduct a deep clone as below:
private static Route GetCurrentBestRoute()
{
Route currentBestRoute = (Route) Ants[0].Route.Clone();
foreach(Ant ant in Ants)
{
if(ant.Route.Distance < currentBestRoute.Distance)
{
currentBestRoute = (Route) ant.Route.Clone();
}
}
return currentBestRoute;
}
But this is not working. currentBestRoute still changes on its own every time the Ants List is updated.
Am I missing something?

public object Clone()
{
Route route = new Route
{
//Cities = Cities
Cities = this.Cities.ToList(),
};
return route;
}

IConeable interface doesn't create deep copy. you can use [Serializable] attribute on class
and use this generic code
public static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T) formatter.Deserialize(ms);
}
}

Related

How can I return a text file content and also the file name?

This return a string that contains the text file content :
public static string LoadSingleRecentFile()
{
DirectoryInfo directoryInfo = new DirectoryInfo(SAVE_FOLDER);
FileInfo[] saveFiles = directoryInfo.GetFiles("*.txt");
FileInfo mostRecentFile = null;
foreach (FileInfo fileInfo in saveFiles)
{
if (mostRecentFile == null)
{
mostRecentFile = fileInfo;
}
else
{
if (fileInfo.LastWriteTime > mostRecentFile.LastWriteTime)
{
mostRecentFile = fileInfo;
}
}
}
if (mostRecentFile != null)
{
string saveString = File.ReadAllText(mostRecentFile.FullName);
return saveString;
}
else
{
return null;
}
}
And here I'm reading the string content :
public void LoadSingleRecentFile()
{
string saveString = SaveSystem.LoadSingleRecentFile();
if (saveString != null)
{
SaveObject saveObject = JsonUtility.FromJson<SaveObject>(saveString);
transform.position = saveObject.position;
transform.localScale = saveObject.scaling;
transform.rotation = saveObject.rotation;
}
}
but instead assign it to a transform like :
transform.position = saveObject.position;
I need to assign it to the object that the string content belong to this object info.
I have a List objectsToSave list type Transform. And I need in the LoadSingleRecentFile to loop over this List find the Transform name that equal to the string content name of the text file and then to assign the position,scaling,rotation to this object transform.
For example :
Instead :
transform.position = saveObject.position;
Then :
objectsToSAve[0].position = saveObject.position
It's just I don't know if objectsToSave[0] is the string content that was read from the text file of this transform.
This is how I'm saving :
public void Save()
{
SaveObject saveObject = new SaveObject();
for (int i = 0; i < objectsToSave.Length; i++)
{
saveObject.position = objectsToSave[i].position;
saveObject.scaling = objectsToSave[i].localScale;
saveObject.rotation = objectsToSave[i].rotation;
string json = JsonUtility.ToJson(saveObject);
SaveSystem.Save(json);
}
}
This is the script attached ot empty gameobject :
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
public class SaveLoad : MonoBehaviour
{
public Transform[] objectsToSave;
public Button saveButton;
private void Awake()
{
SaveSystem.Init();
}
public void Save()
{
SaveObject saveObject = new SaveObject();
for (int i = 0; i < objectsToSave.Length; i++)
{
saveObject.position = objectsToSave[i].position;
saveObject.scaling = objectsToSave[i].localScale;
saveObject.rotation = objectsToSave[i].rotation;
string json = JsonUtility.ToJson(saveObject);
SaveSystem.Save(json);
}
}
public void LoadSingleRecentFile()
{
string saveString = SaveSystem.LoadSingleRecentFile();
if (saveString != null)
{
SaveObject saveObject = JsonUtility.FromJson<SaveObject>(saveString);
transform.position = saveObject.position;
transform.localScale = saveObject.scaling;
transform.rotation = saveObject.rotation;
}
}
public void LoadMultipleFiles()
{
List<string> savedStrings = SaveSystem.LoadMultipleFiles();
for(int i = 0; i < savedStrings.Count; i++)
{
SaveObject saveObject = JsonUtility.FromJson<SaveObject>(savedStrings[i]);
objectsToSave[i].position = saveObject.position;
objectsToSave[i].localScale = saveObject.scaling;
objectsToSave[i].rotation = saveObject.rotation;
}
}
public class SaveObject
{
public Vector3 position;
public Vector3 scaling;
public Quaternion rotation;
}
}
And this is where I'm doing the saving :
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public static class SaveSystem
{
private static readonly string SAVE_FOLDER = Application.dataPath + "/save_";
public static void Init()
{
if (!Directory.Exists(SAVE_FOLDER))
{
Directory.CreateDirectory(SAVE_FOLDER);
}
}
public static void Save(string saveString)
{
int saveNumber = 1;
while (File.Exists(SAVE_FOLDER + "/" + saveNumber + ".txt"))
{
saveNumber++;
}
File.WriteAllText(SAVE_FOLDER + "/" + saveNumber + ".txt", saveString);
}
public static string LoadSingleRecentFile()
{
DirectoryInfo directoryInfo = new DirectoryInfo(SAVE_FOLDER);
FileInfo[] saveFiles = directoryInfo.GetFiles("*.txt");
FileInfo mostRecentFile = null;
foreach (FileInfo fileInfo in saveFiles)
{
if (mostRecentFile == null)
{
mostRecentFile = fileInfo;
}
else
{
if (fileInfo.LastWriteTime > mostRecentFile.LastWriteTime)
{
mostRecentFile = fileInfo;
}
}
}
if (mostRecentFile != null)
{
string saveString = File.ReadAllText(mostRecentFile.FullName);
return saveString;
}
else
{
return null;
}
}
public static List<string> LoadMultipleFiles()
{
DirectoryInfo directoryInfo = new DirectoryInfo(SAVE_FOLDER);
FileInfo[] saveFiles = directoryInfo.GetFiles("*.txt");
List<string> savedFiles = new List<string>();
foreach (FileInfo fileInfo in saveFiles)
{
string savedFile = File.ReadAllText(fileInfo.FullName);
if(savedFile != null)
{
savedFiles.Add(savedFile);
}
}
return savedFiles;
}
}
And a screenshot I have 3 buttons in the scene :
What I want to do is :
When pressing the Save button save all the objects in objectsToSave List create a text file for each object in the List or one big text file for all of them using json.
When pressing the Load Single button just load the changes made for the last gameobject now I'm doing it with LastWriteTime.
When pressing on the button Load Multiple then load all the files that changes have made for example if I have 10 cubes and I moved two each to another position then load this two when pressing multiple depending what objects from the List have changed something position or rotation or scaling or all of them.
To be able to press the save button to enable true/false the buttopn only if one or more objects in the List have made changes moved or rotation or scaling. If there are no changes don't allow to save so the player can't save nonstop and create nonstop save files only on changes.
I don't see where you are saving the object's name. Also currently you are always saving each individual object info thus overwriting the existing save file, right?
You would probably rather do something like
[Serializable]
public class SaveFile
{
public List<SaveObject> objects = new List<SaveObject>();
}
[Serializable]
public class SaveObject
{
public string name;
public Vector3 position;
public Quaternion rotation;
public Vector3 scaling;
}
public void Save()
{
var file = new SaveFile();
foreach (var obj in objectsToSave)
{
var saveObject = new SaveObject();
saveObject.name = obj.name;
saveObject.position = obj.position;
saveObject.scaling = obj.localScale;
saveObject.rotation = obj.rotation;
}
var json = JsonUtility.ToJson(file);
SaveSystem.Save(json);
}
So now you have one file containing all objects information in a form like
{
"objects" : [
{
"name" : "ExampleObject",
"position" : {
"x" : 1.2,
"y" : 0.2,
"z" : 0
},
"rotation" : ...,
"scaling" : ...
},
{
"name" : "Example2",
...
}
...
]
}
So now when loading I assume you still have your objects in the list objectsToSave so you would go e.g.
using System.Linq;
...
public void LoadSingleRecentFile()
{
var json = SaveSystem.LoadSingleRecentFile();
if (!string.IsNullOrWhiteSpace(json))
{
var file = JsonUtility.FromJson<SaveFile>(json);
foreach(var saveObject in file.objects)
{
// Find the object that belongs to this SaveObject
// Either from your list
var obj = objectsToSave.Select(o => obj.gameObject).FirstOrDefault(o => o.name.Equals(saveObject.name));
// Or alternatively directly in the scene
if(!obj) obj = GameObject.Find(saveObject.name);
// Safety check
if(!obj)
{
Debug.LogError($"Could not find /"{saveObject.name}/"");
continue;
}
obj.transform.position = saveObject.position;
obj.transform.localScale = saveObject.scaling;
obj.transform.rotation = saveObject.rotation;
}
}
}
Of course this still requires the objects to have unique names.
To return the information contained in the text file use something like this if you are using python:
text_file = open("beer.txt", "r")
whole_thing = text_file.read()
print(whole_thing)
text_file.close()
In the example the file I wanted to read was beer.txt, and the r means I want to read/display the information contained in it.

How to pass parameters to an api in c#?

I have below api url
http://myapi/api/getproduct?
These parameters will get created from below class
public class ApiParamters
{
public string id {get;set;}
public string name {get;set;}
public List<string> otherNames {get;set;}
}
If value of above parameters are
id=1
name="product1"
OtherNames="oldProduct1" and "oldProduct2"
Then api url should be as below
http://myapi/api/getproduct?id=1&name=product1&OtherNames=oldProduct1&OtherNames=oldProduct2
How to dynamically create these kind of url (GENERIC SOLUTION IS REQUREID because I have to implement simliar logic for other APIs too)
Here's what I came up with using reflection. I'm sure it doesn't handle all the cases, but see if it works for you.
using System.Web;
using System.Reflection;
using System.Collections.Generic;
class Program {
public static string toQueryString(string url, object o)
{
var query = HttpUtility.ParseQueryString(string.Empty);
FieldInfo[] myField = o.GetType().GetFields();
for (int i = 0; i < myField.Length; i++)
{
// Is it a list?
var t = myField[i].GetValue(o) as System.Collections.IEnumerable;
if (t != null)
{
dynamic lst = myField[i].GetValue(o);
int index = 1;
foreach (dynamic oo in lst)
{
query[myField[i].Name + index++] = oo.ToString();
}
}
else
{
query[myField[i].Name] = myField[i].GetValue(o).ToString();
}
}
return query.Count == 0 ? url : url + "?" + query.ToString();
}
}

Serialize class with structs of same type name

I'm trying to XML serialize a class that contains two structs with the same name:
public class MyClass
{
public System.Windows.Size WSize = new System.Windows.Size();
public System.Drawing.Size DSize = new Size.Drawing.Size();
}
The resulting error:
Types 'System.Drawing.Size' and 'System.Windows.Size' both use the XML type name,
'Size', from namespace ''. Use XML attributes to specify a unique XML name and/or
namespace for the type.
Everything I've found so far involves decorating the Type with an XML attribute. I can't directly decorate either struct since they are not my code.
I feel like I'm missing something easy here...Is there an XML attribute that I can apply to the fields?
EDIT
I've added answer using a couple surrogate properties. I'm not happy with that particular implementation since it leaves public properties hanging out.
I've also considered DataContractSerialization but I'm hesitant to take that next step quite yet. Anyone else have something they can suggest?
EDIT 2
There may have been some confusion in my wording. I can modify and decorate MyClass, WSize and DSize. However, perhaps obviously, I cannot modify System.Windows.Size or System.Drawing.Size.
You can do it by proxy with custom XML serialization, I created this fully working example, although there is a lot of error checking to be done its a place to start.
public class MyClass
{
public System.Windows.Size WSize = new System.Windows.Size();
public System.Drawing.Size DSize = new System.Drawing.Size();
}
public class MyClassProxy : MyClass, IXmlSerializable
{
public new System.Windows.Size WSize { get { return base.WSize; } set { base.WSize = value; } }
public new System.Drawing.Size DSize { get { return base.DSize; } set { base.DSize = value; } }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
reader.MoveToContent();
reader.ReadStartElement();
string wheight = reader["height"];
string wwidth = reader["width"];
int w, h;
w = int.Parse(wwidth);
h = int.Parse(wheight);
WSize = new Size(w, h);
reader.ReadStartElement();
string dheight = reader["height"];
string dwidth = reader["width"];
w = int.Parse(dwidth);
h = int.Parse(dheight);
DSize = new System.Drawing.Size(w, h);
}
public void WriteXml(System.Xml.XmlWriter writer)
{
writer.WriteStartElement("MyClassProxy");
writer.WriteStartElement("WSize");
writer.WriteAttributeString("height", WSize.Height.ToString());
writer.WriteAttributeString("width", WSize.Width.ToString());
writer.WriteEndElement();
writer.WriteStartElement("DSize");
writer.WriteAttributeString("height", DSize.Height.ToString());
writer.WriteAttributeString("width", DSize.Width.ToString());
writer.WriteEndElement();
writer.WriteEndElement();
}
}
class Program
{
static void Main(string[] args)
{
MyClassProxy p = new MyClassProxy();
p.DSize = new System.Drawing.Size(100, 100);
p.WSize = new Size(400, 400);
string xml = "";
using (StringWriter sw = new StringWriter())
{
System.Xml.XmlWriter wr = System.Xml.XmlWriter.Create(sw);
p.WriteXml(wr);
wr.Close();
xml = sw.ToString();
}
MyClassProxy p2 = new MyClassProxy();
using (StringReader sr = new StringReader(xml))
{
System.Xml.XmlReader r = System.Xml.XmlReader.Create(sr);
p2.ReadXml(r);
}
MyClass baseClass = (MyClass)p2;
Print(baseClass);
Console.ReadKey();
}
static void Print(MyClass c)
{
Console.WriteLine(c.DSize.ToString());
Console.WriteLine(c.WSize.ToString());
}
}
Here's a possibility that I'm not terribly happy with (not very clean):
public class MyClass
{
public System.Windows.Size WSize = new System.Windows.Size();
[XmlIgnore]
public System.Drawing.Size DSize = new Size();
public int DSizeWidthForSerialization
{
get
{
return DSize.Width;
}
set
{
DSize.Width = value;
}
}
public int DSizeHeightForSerialization
{
get
{
return DSize.Height;
}
set
{
DSize.Height = value;
}
}
}
I ended up creating a new class to house System.Drawing.Size. Within that new class I created implicit operators and handled some of the constructors. This allowed me to serialize and not have to change any existing code:
public class MyClass
{
public System.Windows.Size WSize = new System.Windows.Size();
public MyDrawingSize DSize = new System.Drawing.Size();
public class MyDrawingSize
{
public int Height, Width;
public MyDrawingSize() { } //Needed for deserialization
public MyDrawingSize(int width, int height)
{
Width = width;
Height = height;
}
public static implicit operator System.Drawing.Size(MyDrawingSize size)
{
return new System.Drawing.Size(size.Width, size.Height);
}
public static implicit operator MyDrawingSize(System.Drawing.Size size)
{
return new MyDrawingSize() { Width = size.Width, Height = size.Height };
}
}
}

How to sort an arraylist on date?

Code:
while ((linevalue = filereader.ReadLine()) != null)
{
items.Add(linevalue);
}
filereader.Close();
items.Sort();
//To display the content of array (sorted)
IEnumerator myEnumerator = items.GetEnumerator();
while (myEnumerator.MoveNext())
{
Console.WriteLine(myEnumerator.Current);
}
The program above displays all the values. How to extract only the dates and sort it in ascending order?
I am not let to work with linq, use the exception or threading or any other stuff. I have to stick with the File Stream, try to get my data out of the text file, sort and store it, so that i can retrieve it, view it and edit it and search for any particular date and see the date of joining records for that date. Can't figure out. Struggling
Basically, don't try and work with the file as lines of text; separate that away, so that you have one piece of code which parses that text into typed records, and then process those upstream when you only need to deal with typed data.
For example (and here I'm assuming that the file is tab-delimited, but you could change it to be column indexed instead easily enough); look at how little work my Main method needs to do to work with the data:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
static class Program
{
static void Main()
{
foreach (var item in ReadFile("my.txt").OrderBy(x => x.Joined))
{
Console.WriteLine(item.Names);
}
}
static readonly char[] tab = { '\t' };
class Foo
{
public string Names { get; set; }
public int Age { get; set; }
public string Designation { get; set; }
public DateTime Joined { get; set; }
}
static IEnumerable<Foo> ReadFile(string path)
{
using (var reader = File.OpenText(path))
{
// skip the first line (headers), or exit
if (reader.ReadLine() == null) yield break;
// read each line
string line;
var culture = CultureInfo.InvariantCulture;
while ((line = reader.ReadLine()) != null)
{
var parts = line.Split(tab);
yield return new Foo
{
Names = parts[0],
Age = int.Parse(parts[1], culture),
Designation = parts[2],
Joined = DateTime.Parse(parts[3], culture)
};
}
}
}
}
And here's a version (not quite as elegant, but working) that works on .NET 2.0 (and probably on .NET 1.1) using only ISO-1 language features; personally I think it would be silly to use .NET 1.1, and if you are using .NET 2.0, then List<T> would be vastly preferable to ArrayList. But this is "worst case":
using System;
using System.Collections;
using System.Globalization;
using System.IO;
class Program
{
static void Main()
{
ArrayList items = ReadFile("my.txt");
items.Sort(FooByDateComparer.Default);
foreach (Foo item in items)
{
Console.WriteLine(item.Names);
}
}
class FooByDateComparer : IComparer
{
public static readonly FooByDateComparer Default
= new FooByDateComparer();
private FooByDateComparer() { }
public int Compare(object x, object y)
{
return ((Foo)x).Joined.CompareTo(((Foo)y).Joined);
}
}
static readonly char[] tab = { '\t' };
class Foo
{
private string names, designation;
private int age;
private DateTime joined;
public string Names { get { return names; } set { names = value; } }
public int Age { get { return age; } set { age = value; } }
public string Designation { get { return designation; } set { designation = value; } }
public DateTime Joined { get { return joined; } set { joined = value; } }
}
static ArrayList ReadFile(string path)
{
ArrayList items = new ArrayList();
using (StreamReader reader = File.OpenText(path))
{
// skip the first line (headers), or exit
if (reader.ReadLine() == null) return items;
// read each line
string line;
CultureInfo culture = CultureInfo.InvariantCulture;
while ((line = reader.ReadLine()) != null)
{
string[] parts = line.Split(tab);
Foo foo = new Foo();
foo.Names = parts[0];
foo.Age = int.Parse(parts[1], culture);
foo.Designation = parts[2];
foo.Joined = DateTime.Parse(parts[3], culture);
items.Add(foo);
}
}
return items;
}
}
I'm not sure why you'd want to retrieve just the dates. You'd probably be better reading your data into Tuples first. Something like
List<Tuple<string, int, string, DateTime>> items.
Then you can sort them by items.Item4, which will be the date.
You can use LINQ and split the line according to tabs to only retrieve the date and order them through a conversion to date.
while ((linevalue = filereader.ReadLine()) != null)
{
items.Add(linevalue.Split('\t').Last());
}
filereader.Close();
items.OrderBy(i => DateTime.Parse(i));
foreach(var item in items)
{
Console.WriteLine(item);
}
get the desired values in Array from the file...
public class DateComparer : IComparer {
public int Compare(DateTime x, DateTime y) {
if(x.Date > y.Date)
return 1;
if(x.Date < y.Date)
return -1;
else
return 0;
}
}
list.Sort(new DateComparer());

XML Serialization of List growing too large

I have a C# Windows forms application that runs a Trivia game on an IRC channel, and keeps the questions it asks, and the Leaderboard (scores) in Classes that I serialize to XML to save between sessions. The issue I have been having is best described with the flow, so here it is:
User X Gets entry in Leaderboard class with a score of 1. Class is saved to XML, XML contains one entry for user X.
User Y gets entry in Leaderboard class with a score of 1. Class is saved to XML, XML contains duplicate entries for User X, and one entry for User Y.
After running it for a week with under 20 users, I hoped to be able to write a web backend in PHP to help me use the scores. XML file is 2 megabytes.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
namespace IRCTriviaBot
{
[Serializable()]
public class LeaderBoard
{
[Serializable()]
public class Pair
{
public string user;
public int score;
public Pair(string usr, int scr)
{
user = usr;
score = scr;
}
public Pair() { }
}
private static List<Pair> pairs = null;
public List<Pair> Pairs
{
get
{
if (pairs==null)
{
pairs = new List<Pair>();
}
return pairs;
}
}
public LeaderBoard()
{
}
public void newScore(string usr)
{
bool found = false;
for (int i = 0; i < Pairs.Count && !found; ++i)
{
if (Pairs[i].user==usr)
{
found = true;
Pairs[i].score++;
}
}
if (!found)
{
Pairs.Add(new Pair(usr, 1));
}
}
public int getScore(string usr)
{
bool found = false;
for (int i = 0; i < Pairs.Count && !found; ++i)
{
if (Pairs[i].user == usr)
{
return Pairs[i].score;
}
}
if (!found)
{
return 0;
}
return 0;
}
}
}
Here's where the serialization and deserialization happens.
void parseMessage(string message, string user = "")
{
if (message == "-startgame-")
{
if (!gameStarted)
{
gameStarted = true;
openScores();
startGame();
}
}
else if (message == "-hint-")
{
if (!hintGiven && gameStarted)
{
sendMessage("Here's a better hint: " + Form2.qa.Answers[curQ].Trim());
hintGiven = true;
}
}
else if (message == "-myscore-")
{
sendMessage(user + ", your score is: " + leaderB.getScore(user));
}
else if (message.ToLower() == Form2.qa.Answers[curQ].ToLower())
{
if (gameStarted)
{
sendMessage(user + " got it right! Virtual pat on the back!");
leaderB.newScore(user);
saveScores();
System.Threading.Thread.Sleep(2000);
startGame();
}
}
else if (message == "-quit-")
{
if (gameStarted)
{
sendMessage("Sorry to see you go! Have fun without me :'(");
gameStarted = false;
}
else
{
sendMessage("A game is not running.");
}
}
else
{
if (gameStarted)
{
//sendMessage("Wrong.");
}
}
}
void saveScores()
{
//Opens a file and serializes the object into it in binary format.
Stream stream = System.IO.File.Open("scores.xml", FileMode.Open);
XmlSerializer xmlserializer = new XmlSerializer(typeof(LeaderBoard));
//BinaryFormatter formatter = new BinaryFormatter();
xmlserializer.Serialize(stream, leaderB);
stream.Close();
}
void openScores()
{
Stream stream = System.IO.File.OpenRead("scores.xml");
XmlSerializer xmlserializer = new XmlSerializer(typeof(LeaderBoard));
//BinaryFormatter formatter = new BinaryFormatter();
leaderB = (LeaderBoard)xmlserializer.Deserialize(stream);
stream.Close();
}
I think this has to do with pairs being marked static. I don't believe the XmlSerializer will clear a list before adding elements to it, so every time you call openScores() you will create duplicate entries rather than overwrite existing ones.
In general, I've observed that serialization and global variables don't play well together. For this purpose, "global variables" includes private statics, singletons, monostate classes like this, and thread-local variables.
It also looks like there's some waffle here between using XML and binary serialization. They are completely different beasts. XML serialization looks only at a class's public properties, while binary serialization looks only at a class's instance fields. Also, XML serialization ignores the Serializable attribute.

Categories