Today I hit an issue while using a reference in C#. My use-case is the following: I have a global configuration-object in a winform-application. When opening the settings-screen, the settings-part of the object gets deep-cloned. At the time of clicking the save button, the element should get saved back to the original reference. This is not working as I first expected it to.
I have prepared a dotnet-fiddle for you to take a look at the problem. If someone is aware of a way to solve this problem, I am more than thankful for your input. I am thankful for pointing me to the correction too (I am sure someone has solved this issue in a much much cleaner way).
1) We have an globally avaiable static object structure
2) At a point in time, a part of this structure should be deattached from the object structure
3) The deattached part will be edited
4) At a later point in time, the part MAYBE should be reattached to the original reference in the object structure again
I am a frontend developer and its pretty hard to express my problem with words, so take a look at my code:
https://dotnetfiddle.net/qzJqC4
--- For readers from the far future where links are a thing of the past ---
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
namespace DemoCloneAndReattach
{
[Serializable]
public class DogFamily
{
public DogFamily()
{
PrettyDogs = new List<PrettyDog>();
}
public List<PrettyDog> PrettyDogs { get; set; }
}
[Serializable]
public class PrettyDog
{
public string Name { get; set; }
public int NumberOfEars { get; set; }
}
public class Program
{
public static void Main(string[] args)
{
// Note: The WriteLines will help you to validate the correctness of the solution. Thanks a lot!
// Setting up
DogFamily dogFamily = new DogFamily();
dogFamily.PrettyDogs.Add(new PrettyDog() { Name = "OriginalDog", NumberOfEars = 2 });
// Getting the dog with the name "OriginalDog"
PrettyDog originalDog = dogFamily.PrettyDogs.FirstOrDefault(o => o.Name == "OriginalDog");
originalDog.NumberOfEars = 3;
Console.WriteLine("originalDog has NumberOfEars (expected 3): " + originalDog.NumberOfEars);
// Checking if the originalDog in the List has the value updated (from 2 to 3). It does.
PrettyDog checkDogOne = dogFamily.PrettyDogs.FirstOrDefault(o => o.Name == "OriginalDog");
Console.WriteLine();
Console.WriteLine("checkDogOne has NumberOfEars (expected 3): " + checkDogOne.NumberOfEars);
// Now creating a deep-clone of the originalDog (this will result in a brand new object, not related to the DogFamily in any way)
PrettyDog clonedDog = DeepClone<PrettyDog>(originalDog);
// Doing something that should not YET be written to the DogFamily-reference tree.
clonedDog.NumberOfEars = 7;
// Checking if the clonedDog has not the same reference as the originalDog. As expected, it hasn't.
Console.WriteLine();
Console.WriteLine("clonedDog has NumberOfEars (expected 7): " + clonedDog.NumberOfEars);
Console.WriteLine("originalDog still has NumberOfEars (expected 3): " + originalDog.NumberOfEars);
// I want the behavior below, but automated (some kind of reverse-matching or -reattaching-logic to the reference of originalDog):
originalDog.Name = clonedDog.Name;
originalDog.NumberOfEars = clonedDog.NumberOfEars;
// Maybe the call to the solution would look like this:
// Reattach<PrettyDog>(originalDog, clonedDog);
Console.WriteLine();
Console.WriteLine("originalDog now has NumberOfEars (expected 7): " + originalDog.NumberOfEars);
Console.WriteLine("clonedDog has still NumberOfEars (expected 7): " + clonedDog.NumberOfEars);
// Checking if the reference is set correctly (not only the originalDog-reference but the whole reference-tree)
PrettyDog checkDogTwo = dogFamily.PrettyDogs.FirstOrDefault(o => o.Name == "OriginalDog");
Console.WriteLine();
Console.WriteLine("checkDogTwo has NumberOfEars (expected 7 - this is the tricky one): " + checkDogTwo.NumberOfEars);
}
public static T DeepClone<T>(T obj)
{
if (obj == null) return default(T);
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
public static void Reattach<T>(T original, T clone)
{
// Logic for replacing the original with the clone without damaging the reference-tree
}
}
}
Related
Created a simple class with one element, a string [] array and filled it with elements. I want to be able to add and delete elements in this array anywhere in my application, but it is not accessable. been trying variations for two days so coming to the best for help.
enter code here
public static void TestSlots()
{
String currentBehavior = _core.GetVariable("$_current_name", false);
bool success1 = _core.ApiApp().SetDiagOutput("MYWARNING " + currentBehavior + " is");
int indexNumber = MedIntents.medIntents.IndexOf(currentBehavior);
;
if (indexNumber < 0)
{
return;
}
else
{
//Global.MedIntents.RemoveAt(indexNumber);
bool success = _core.ApiApp().SetDiagOutput("MYWARNING " + currentBehavior + " removed");
}
//now check if they asked or hit more than one important entity
return;
}
// Console.WriteLine("Hello World!");
}
internal class MedIntents
{
public string[] medIntents = new string[] {
"any_chills",
"constant_or_intermittent",
"gradual_or_sudden",
"had_them_before",
"how_often",
"howdoesitstart",
"hurt_elsewhere",
"nausea_or_vomitting",
"numbness",
"pain_relief",
"relation_to_food_or_medical",
"scaleofone2ten",
"warning_signs"
};
medIntents is an instance member. You may want to convert it to a static member. To prevent modification, you can add the readonly modifier. – Crafted Pod 2 hours ago
So i'm making a basic 2D platformer in Unity, I want to be able to save the time it takes the player to complete each level and display the fastest time in a UI element. I'm writing the times to a text file (this works fine), and reading all the times line by line into a list, from there I find the lowest value etc. However, my code does not work, it gives me the following error when I'm calling the function from another script. I'm new to C# so would greatly appreciate any help anyone can give me!
Thanks!
Full Error Message
InvalidOperationException: Operation is not valid due to the current state of the object
System.Linq.Enumerable.Iterate[Single,Single] (IEnumerable1 source, Single initValue, System.Func3 selector)
System.Linq.Enumerable.Min (IEnumerable`1 source)
SaveScores.ReadData (System.String LevelLoaded) (at Assets/Scripts/Highscores/SaveScores.cs:73)
GameManager.Update () (at Assets/Scripts/GameManager.cs:45)
Code
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System;
using System.Linq;
public class SaveScores : MonoBehaviour {
void Start()
{
//ReadData();
}
public static void WriteData(float time, string LevelLoaded)
{
try
{
//Debug.Log("Saving time");
StreamWriter sw = new StreamWriter(#"C:\Users\Theo\Documents\Unity Projects\V13\Platformer\Assets\Scripts\Highscores\Scores.txt", true);
sw.WriteLine(LevelLoaded + " " + time);
sw.Close();
}
catch(Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
finally
{
Console.WriteLine("Executing final block");
}
}
public static void ReadData(string LevelLoaded)
{
List <float> timesLevel1 = new List<float>();
List <float> timesLevel2 = new List<float>();
List <float> timesLevel3 = new List<float>();
try
{
var lines = File.ReadAllLines(#"C:\Users\Theo\Documents\Unity Projects\V13\Platformer\Assets\Scripts\Highscores\Scores.txt");
foreach (var line in lines)
{
if (line.Contains("Level1"))
{
timesLevel1.Add(Convert.ToSingle(line));
}
else if (line.Contains("Level2"))
{
timesLevel2.Add(Convert.ToSingle(line));
}
else if (line.Contains("Level3"))
{
timesLevel3.Add(Convert.ToSingle(line));
}
}
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e.Message);
}
finally
{
Console.WriteLine("Executing final block");
}
switch (LevelLoaded)
{
case "Level1":
UIManager.lowestTime = timesLevel1.Min();
break;
case "Level2":
UIManager.lowestTime = timesLevel2.Min();
break;
case "Level3":
UIManager.lowestTime = timesLevel3.Min();
break;
}
}
}
If you look at the documentation for Enumerable.Min<float> (https://msdn.microsoft.com/en-us/library/bb361144(v=vs.110).aspx) you will see that under "Exceptions" it lists the InvalidOperationException and gives the reason as: "source contains no elements".
What this likely means is that the list you are looking at doesn't contain any elements.
On looking at what is populating that list there seems to be some confusion in what you expect to be in the line. You do a test for line.Contains("Level1") but if that succeeds then you call Convert.ToSingle(line) which would fail if there was any string data in that line (eg if line did contain that string then the convert would fail).
So it seems likely that the format of the file you are reading is not what you expect it to be leading to your lists being empty leading to this error.
My code:
public class CLASS_A {
public static Dictionary<int, CLASS_A> List = new Dictionary<int, CLASS_A>;
public static PP_CLASS pp = null;
public static CLASS_A ID
{
get
{
int key = get_threadID;
if (List.ContainsKey(key))
return List[key];
else
return null;
}
set
{
int key = get_threadID;
List[key] = value;
}
}
public virtual void init(lib, name)
{
...
if (name != "")
{
if (pp == null)
PP = this;
}
...
}
}
So whichever thread calls init, it's id is used to store this (whoever called). Eg my list looks like this:
45 = CLASS_A_object0
67 = CLASS_A_object1
...
But now when a different thread calls a method on pp, say CLASS_A.pp.setWelcome, this will return null for pp, and throws null exception! Because when set is called the thread id will be different and won't be in the list.
So is it possible that I know which object called so that I can do reverse lookup? Or maybe a different solution?
Why I want this:
Initially we were connecting to one device so that was ok. Now there are multiple devices with each one having its own ip/port. Initial code had just public static PP_CLASS pp = null; So others will be just calling methods on pp using class name and things were good.
Previous behaviour: The software picks list of devices from a file and since pp is static it only talks to the first device. I added that pp==null line which i forgot in my initial post. So when the code starts pp==null will be true and first device is assigned, but now for other devices pp==null will be false and thus I am not able to talk to other devices.
Please let me know if more details are needed.
Beginning with C# 5.0 (August 2012) there is a new feature "Caller Info Attribute". If your classes are stored in separate files, you can make use of the CallerFilePathAttribute to register which class actually has called.
Example from MSDN:
// using System.Runtime.CompilerServices
// using System.Diagnostics;
public void DoProcessing()
{
TraceMessage("Something happened.");
}
public void TraceMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string sourceFilePath = "",
[CallerLineNumber] int sourceLineNumber = 0)
{
Trace.WriteLine("message: " + message);
Trace.WriteLine("member name: " + memberName);
Trace.WriteLine("source file path: " + sourceFilePath);
Trace.WriteLine("source line number: " + sourceLineNumber);
}
// Sample Output:
// message: Something happened.
// member name: DoProcessing
// source file path: c:\Users\username\Documents\Visual Studio 2012\Projects\CallerInfoCS\CallerInfoCS\Form1.cs
// source line number: 31
You could try examining the Stack Trace.
var trace = new System.Diagnostics.StackTrace();
or to get the calling line only it should be something like:
var caller = new System.Diagnostics.StackTrace().GetFrame(1)
So! I've got a C# array.
And I've got a function that returns an element from the array, so the data from that reference can be accessed. Yay!
It would be really super awesome convenient if changing that reference then affected that original element in the array. Is this what static variables do? Is there a way to do it? How to do? For example:
Function A finds an item:
public TutorialPopupBehavior GetBehavior(string behaviorName) {
foreach(TutorialPopupBehavior beh in _tutorialItems) {
if(beh._popupName == behaviorName) {
return beh;
}
}
print ("Could not find behavior of name " + behaviorName);
return null;
}
And then returns it to function B, which then, ideally, would be able to change a property of the returned item:
public void SetTutorialItem(bool state, string itemName) {
TutorialPopupBehavior beh = GetBehavior(itemName);
if(beh == null) {
print ("No tutorial item found, so can't set it to " + state);
return;
}
//idealistic code: beh._isShown = true;
}
The _isShown property of that element would then be changed permanently in the original _tutorialItems array...how do you all accomplish this, or design differently, so as to avoid the problem? The reason I ask is because I have a number of arrays to search, and I don't want to complicate my code by asking the same class to search through the same set of arrays more than once.
public void GetBehavior(string behaviorName, ref TutorialPopupBehavior b) {
foreach(TutorialPopupBehavior beh in _tutorialItems) {
if(beh._popupName == behaviorName) {
b = beh;
Return;
}
}
print ("Could not find behavior of name " + behaviorName);
b = null;
}
Read this msdn article
I been having trouble trying to figure this out. When I think I have it I get told no. Here is a picture of it.
I am working on the save button. Now after the user adds the first name, last name and job title they can save it. If a user loads the file and it comes up in the listbox, that person should be able to click on the name and then hit the edit button and they should be able to edit it. I have code, but I did get inform it looked wackey and the string should have the first name, last name and job title.
It is getting me really confused as I am learning C#. I know how to use savefiledialog but I am not allowed to use it on this one. Here is what I am suppose to be doing:
When the user clicks the “Save” button, write the selected record to
the file specified in txtFilePath (absolute path not relative) without
truncating the values currently inside.
I am still working on my code since I got told that it will be better file writes records in a group of three strings. But this is the code I have right now.
private void Save_Click(object sender, EventArgs e)
{
string path = txtFilePath.Text;
if (File.Exists(path))
{
using (StreamWriter sw = File.CreateText(path))
{
foreach (Employee employee in employeeList.Items)
sw.WriteLine(employee);
}
}
else
try
{
StreamWriter sw = File.AppendText(path);
foreach (var item in employeeList.Items)
sw.WriteLine(item.ToString());
}
catch
{
MessageBox.Show("Please enter something in");
}
Now I can not use save or open file dialog. The user should be able to open any file on the C,E,F drive or where it is. I was also told it should be obj.Also the program should handle and exceptions that arise.
I know this might be a noobie question but my mind is stuck as I am still learning how to code with C#. Now I have been searching and reading. But I am not finding something to help me understand how to have all this into 1 code. If someone might be able to help or even point to a better web site I would appreciate it.
There are many, many ways to store data in a file. This code demonstrates 4 methods that are pretty easy to use. But the point is that you should probably be splitting up your data into separate pieces rather than storing them as one long string.
public class MyPublicData
{
public int id;
public string value;
}
[Serializable()]
class MyEncapsulatedData
{
private DateTime created;
private int length;
public MyEncapsulatedData(int length)
{
created = DateTime.Now;
this.length = length;
}
public DateTime ExpirationDate
{
get { return created.AddDays(length); }
}
}
class Program
{
static void Main(string[] args)
{
string testpath = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "TestFile");
// Method 1: Automatic XML serialization
// Requires that the type being serialized and all its serializable members are public
System.Xml.Serialization.XmlSerializer xs =
new System.Xml.Serialization.XmlSerializer(typeof(MyPublicData));
MyPublicData o1 = new MyPublicData() {id = 3141, value = "a test object"};
MyEncapsulatedData o2 = new MyEncapsulatedData(7);
using (System.IO.StreamWriter w = new System.IO.StreamWriter(testpath + ".xml"))
{
xs.Serialize(w, o1);
}
// Method 2: Manual XML serialization
System.Xml.XmlWriter xw = System.Xml.XmlWriter.Create(testpath + "1.xml");
xw.WriteStartElement("MyPublicData");
xw.WriteStartAttribute("id");
xw.WriteValue(o1.id);
xw.WriteEndAttribute();
xw.WriteAttributeString("value", o1.value);
xw.WriteEndElement();
xw.Close();
// Method 3: Automatic binary serialization
// Requires that the type being serialized be marked with the "Serializable" attribute
using (System.IO.FileStream f = new System.IO.FileStream(testpath + ".bin", System.IO.FileMode.Create))
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
bf.Serialize(f, o2);
}
// Demonstrate how automatic binary deserialization works
// and prove that it handles objects with private members
using (System.IO.FileStream f = new System.IO.FileStream(testpath + ".bin", System.IO.FileMode.Open))
{
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bf =
new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
MyEncapsulatedData o3 = (MyEncapsulatedData)bf.Deserialize(f);
Console.WriteLine(o3.ExpirationDate.ToString());
}
// Method 4: Manual binary serialization
using (System.IO.FileStream f = new System.IO.FileStream(testpath + "1.bin", System.IO.FileMode.Create))
{
using (System.IO.BinaryWriter w = new System.IO.BinaryWriter(f))
{
w.Write(o1.id);
w.Write(o1.value);
}
}
// Demonstrate how manual binary deserialization works
using (System.IO.FileStream f = new System.IO.FileStream(testpath + "1.bin", System.IO.FileMode.Open))
{
using (System.IO.BinaryReader r = new System.IO.BinaryReader(f))
{
MyPublicData o4 = new MyPublicData() { id = r.ReadInt32(), value = r.ReadString() };
Console.WriteLine("{0}: {1}", o4.id, o4.value);
}
}
}
}
As you are writing the employee objects with WriteLine, the underlying ToString() is being invoked. What you have to do first is to customize that ToString() methods to fit your needs, in this way:
public class Employee
{
public string FirstName;
public string LastName;
public string JobTitle;
// all other declarations here
...........
// Override ToString()
public override string ToString()
{
return string.Format("'{0}', '{1}', '{2}'", this.FirstName, this.LastName, this.JobTitle);
}
}
This way, your writing code still keeps clean and readable.
By the way, there is not a reverse equivalent of ToSTring, but to follow .Net standards, I suggest you to implement an Employee's method like:
public static Employee Parse(string)
{
// your code here, return a new Employee object
}
You have to determine a way of saving that suits your needs. A simple way to store this info could be CSV:
"Firstname1","Lastname 1", "Jobtitle1"
" Firstname2", "Lastname2","Jobtitle2 "
As you can see, data won't be truncated, since the delimiter " is used to determine string boundaries.
As shown in this question, using CsvHelper might be an option. But given this is homework and the constraints therein, you might have to create this method yourself. You could put this in Employee (or make it override ToString()) that does something along those lines:
public String GetAsCSV(String firstName, String lastName, String jobTitle)
{
return String.Format("\"{0}\",\"{1}\",\"{2}\"", firstName, lastName, jobTitle);
}
I'll leave the way how to read the data back in as an exercise to you. ;-)