I'm creating a software product which is an AVLTree containing the details of Authors. The Author class contains: Name, Year Of Publish and List of Books (using LinkedList<> collection). The Author objects will be stored in the AVLTree with the Name as the key for comparison.
My problem is that I can't seem to store the Author class correctly in the AVLTree.
I appreciate any advice and help.
I create the Author array, and create an AVLTree:
public Author[] author = new Author[i];
public AVLTree<Author> authorAVL = new AVLTree<Author>();
The code for the 'Add Author' button is as follows:
author[i].Name = textBoxAddAuthor.Text;
author[i].YrOfPub = textBoxYrOfPub.Text;
author[i] = new Author(author[i].Name, author[i].YrOfPub);
Array.Sort(author);
authorAVL.InsertItem(artist[i]);
I've implemented CompareTo in the Author class as follows:
public int CompareTo(object obj)
{
if (obj is Author) //compare by name
{
Author other = (Author)obj;
return name.CompareTo(other.name);
}
The InsertItem method in the AVLTree looks like this:
public void InsertItem(T item)
{
insertItem(item, ref root);
}
private void insertItem(T item, ref Node<T> tree)
{
if (tree == null)
tree = new Node<T>(item);
else if (item.CompareTo(tree.Data) < 0)
insertItem(item, ref tree.Left);
else if (item.CompareTo(tree.Data) > 0)
insertItem(item, ref tree.Right);
tree.BalanceFactor = (height(tree.Left) - height(tree.Right));
if (tree.BalanceFactor <= -2)
rotateLeft(ref tree);
if (tree.BalanceFactor >= 2)
rotateRight(ref tree);
}
And the node class includes this:
public class Node<T> where T : IComparable
{
private T data;
public Node<T> Left, Right;
private int balanceFactor = 0;
public Node(T item)
{
data = item;
Left = null;
Right = null;
}
public T Data
{
set { data = value; }
get { return data; }
}
public int BalanceFactor
{
set { balanceFactor = value; }
get { return balanceFactor; }
}
}
It seems to me the problem is here:
author[i].Name = textBoxAddAuthor.Text;
author[i].YrOfPub = textBoxYrOfPub.Text;
author[i] = new Author("Name", "yearofpublish");
In particular the order of operations is not right. You are attempting to set properties of author[i] and then you are overwriting that with a new instance of Author.. doesn't make any sense.
It should be:
author[i] = new Author(textBoxAddAuthor.Text, textBoxYrOfPub.Text);
I'm also a bit confused about three other things in your code:
Why do you also have an array holding Authors if you are putting them in the tree in the first place?
Why are you initializing the array of autors like this: public Author[] author = new Author[i];. Where does i come from?
Why are you sorting the array each time you want to insert into the tree? The tree is self-balancing..
And then you are re-using i to initialize/set up the author before inserting into tree.. ?!
To me the following block:
// where does this i come from here?
author[i].Name = textBoxAddAuthor.Text; // this is useless..
author[i].YrOfPub = textBoxYrOfPub.Text; // this is useless..
author[i] = new Author(author[i].Name, author[i].YrOfPub); // overwriting author[i] here
Array.Sort(author); // why are you sorting the array each time you insert?
authorAVL.InsertItem(artist[i]);
Should be re-written as:
Author newAuthor = new Author(textBoxAddAuthor.Text, textBoxYrOfPub.Text);
authorAVL.InsertItem(newAuthor);
Related
Well, I would like to do my own benchmarking system like spark in Minecraft (https://github.com/lucko/spark):
I'm using Harmony lib (https://github.com/pardeike/Harmony) which allows me to interact/modify methods and allows me to add a Prefix/Postfix on each call that will help me out with this stack.
The basic structure has something similar to (https://github.com/pardeike/Harmony/issues/355):
[HarmonyPatch]
class MyPatches
{
static IEnumerable<MethodBase> TargetMethods()
{
return AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly())
.SelectMany(type => type.GetMethods())
.Where(method => method.ReturnType != typeof(void) && method.Name.StartsWith("Do"));
}
static void Prefix(out Stopwatch __state, MethodBase __originalMethod)
{
__state = Stopwatch.StartNew();
// ...
}
static void Postfix(Stopwatch __state, MethodBase __originalMethod)
{
__state.Stop();
// ....
}
}
The problem here is that the __originalMethod doesn't take care if it was called from A or B.
So for example, we had patched string.Join method. And the we call from A or B, where A or B, is the full callstack of this method.
So first, we need to assign a ID to this call, and we need to create a Tree-based structure (which is hard to serialize later), from here (https://stackoverflow.com/a/36649069/3286975):
public class TreeModel : Tree<TreeModel>
{
public int ID { get; set; }
public TreeModel() { }
public TreeModel(TreeModel parent) : base(parent) { }
}
public class Tree<T> where T : Tree<T>
{
protected Tree() : this(null) { }
protected Tree(T parent)
{
Parent=parent;
Children=new List<T>();
if(parent!=null)
{
parent.Children.Add(this as T);
}
}
public T Parent { get; set; }
public List<T> Children { get; set; }
public bool IsRoot { get { return Parent==null; } }
public T Root { get { return IsRoot?this as T:Parent.Root; } }
public T RecursiveFind(Predicate<T> check)
{
if(check(this as T)) return this as T;
foreach(var item in Children)
{
var result=item.RecursiveFind(check);
if(result!=null)
{
return result;
}
}
return null;
}
}
Now, the thing is that we need to fill the Tree as long as we iterate all the method and instructions got from Harmony. Forget about Harmony for a second, I will explain only two facts about it.
The lib allows you first to get all patched methods through IEnumerable<MethodBase> TargetMethods() so, you have the Assembly X passed through reflection and filtered all methods that are allowed to be patched (some of them broke Unity, so I decided to skip methods from UnityEngine., UnityEditor. and System.* namespaces).
And we have also the ReadMethodBody method (https://harmony.pardeike.net/api/HarmonyLib.PatchProcessor.html#HarmonyLib_PatchProcessor_ReadMethodBody_System_Reflection_MethodBase_) from a given MethodBase it returns all IL stack instructions.
So we can start to iterate over and over in order to get all instructions and fill the entire tree. This is what I wrote last night:
internal static class BenchmarkEnumerator
{
internal static Dictionary<MethodBase, int> Mappings { get; } = new Dictionary<MethodBase, int>();
internal static Dictionary<int, TreeModel> TreeIDs { get; } = new Dictionary<int, TreeModel>();
internal static Dictionary<MethodBase, BenchmarkTreeModel> TreeMappings { get; } = new Dictionary<MethodBase, BenchmarkTreeModel>();
private static HashSet<int> IDUsed { get; } = new HashSet<int>();
public static int GetID(this MethodBase method)
{
return GetID(method, out _);
}
public static int GetID(this MethodBase method, out bool contains)
{
// A > X = X1
// B > X = X2
if (!Mappings.ContainsKey(method))
{
var id = Mappings.Count;
Mappings.Add(method, Mappings.Count);
IDUsed.Add(id);
contains = false;
return id;
}
contains = true;
return Mappings[method];
}
public static int GetFreeID()
{
int id;
Random rnd = new Random();
do
{
id = rnd.Next();
} while (IDUsed.Contains(id));
IDUsed.Add(id);
return id;
}
public static BenchmarkCall GetCall(int id)
{
return TreeIDs[id]?.Call;
}
public static BenchmarkCall GetCall(this MethodBase method)
{
return TreeIDs[Mappings[method]]?.Call;
}
}
The BenchmarkEnumerator class allow us to differentiate between A or B, but it doesn't care about the full hierarchy, only from the parent MethodBase itself, so I need to write something complex to take in care of the full call stack, which I said I have a problem to understand.
Then we have the TargetMethods:
private static IEnumerable<MethodBase> TargetMethods()
{
Model = new BenchmarkTreeModel();
var sw = Stopwatch.StartNew();
//int i = 0;
return Filter.GetTargetMethods(method =>
{
try
{
var instructions = PatchProcessor.ReadMethodBody(method);
var i = method.GetID(out var contains);
var tree = new TreeModel
{
ID = i
};
if (contains)
{
//var lastId = i;
i = GetFreeID();
tree.ID = i;
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
tree.Parent = null;
tree.Children = TreeMappings[method].Forest.First().Children; // ??
//DictionaryHelper.AddOrAppend(TreeMappings, method, tree);
TreeMappings[method].Forest.Add(tree);
TreeIDs.Add(i, tree);
Model.Forest.Add(tree);
// UNIT TESTING: All contained methods at this point will have a parent.
// string.Join is being added as a method by a instruction, so when we try to patch it, it will have already a reference on the dictionary
// Here, we check if the method was already added by a instruction CALL
// Logic: If the method is already contained by the mapping dictionary
// then, we will exit adding a new that will have the same childs but a new ID
return false;
}
TreeIDs.Add(i, tree);
tree.FillMethodName($"{method.GetMethodSignature()}_{i}"); // TODO: Check this
foreach (var pair in instructions)
{
var opcode = pair.Key;
if (opcode != OpCodes.Call || opcode != OpCodes.Callvirt) continue;
var childMethod = (MethodBase)pair.Value;
var id = childMethod.GetID(out var _contains);
var subTree = new TreeModel(tree)
{
ID = id
};
if (_contains)
{
id = GetFreeID();
subTree.ID = id;
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}"); // TODO: Check this
subTree.Parent = TreeIDs[i];
subTree.Children = TreeMappings[childMethod].Forest.First().Children;
TreeIDs.Add(id, subTree);
continue;
}
TreeIDs.Add(id, subTree);
subTree.FillMethodName($"{childMethod.GetMethodSignature()}_{id}");
tree.Children.Add(subTree);
TreeMappings.Add(childMethod, new BenchmarkTreeModel());
TreeMappings[childMethod].Forest.Add(subTree);
}
TreeMappings.Add(method, new BenchmarkTreeModel());
TreeMappings[method].Forest.Add(tree);
Model.Forest.Add(tree);
return true;
//var treeModel = new TreeModel();
}
catch (Exception ex)
{
//Debug.LogException(new Exception(method.GetMethodSignature(), ex));
return false;
}
}, sw);
//return methods;
}
The GetMethodSignature is something like:
public static string GetMethodSignature(this MethodBase method)
{
if (method == null) return null;
return method.DeclaringType == null ? method.Name : $"{method.DeclaringType.FullName}.{method.Name}";
}
I think I'll replace it with the MethodBase.ToString instead (what do you think?)
Also, we have the BenchmarkCall class which allow us to take in care how many times the call was done and how many time it has spent at all:
[Serializable]
public class BenchmarkCall
{
public string Method { get; set; }
public double SpentMilliseconds { get; set; }
public long SpentTicks { get; set; }
public double MinSpentMs { get; set; } = double.MaxValue;
public double MaxSpentMs { get; set; } = double.MinValue;
public long MinSpentTicks { get; set; } = long.MaxValue;
public long MaxSpentTicks { get; set; } = long.MinValue;
public double AvgMs => SpentMilliseconds / TimesCalled;
public double AvgTicks => SpentTicks / (double)TimesCalled;
public BenchmarkCall()
{
}
public BenchmarkCall(MethodBase method)
{
Method = method.GetMethodSignature();
}
public override string ToString()
{
if (TimesCalled > 0)
return "BenchmarkCall{\n" +
$"Ticks[SpentTicks={SpentTicks},MinTicks={MinSpentTicks},MaxTicks={MaxSpentTicks},AvgTicks={AvgTicks:F2}]\n" +
$"Ms[SpentMs={SpentMilliseconds:F2},MinMs={MinSpentMs:F2},MaxMs={MaxSpentMs:F2},AvgMs={AvgMs:F2}]\n" +
"}";
return "BenchmarkCall{}";
}
}
}
So I think that my next movement will be to differentiate between X method being called from A or B (Xa or Xb) taking care of the full hierarchy (which I'm not sure how to do) instead of the parent method that calls it, maybe the code I wrote has some to do it with it, but I'm not sure (last night I was so tired, so I didn't code it taking care those facts), build up a list of method signatures with different IDs, and then fill up the tree, ID 1 is Xa and ID 2 is Xb (where I have problems also filling up the tree).
Also I'll need to use the Transpiler in order to alter all code instructions, so if a method has:
void method() {
X1();
X2();
}
We will need to add 2 methods (like prefix/postfix) to measure each instruction call:
void method() {
Start(1);
X1();
End(1);
Start(2);
X2();
End(2);
}
This will be a hard task, but I hope somebody could guide me with this out.
I have created a simple list class from scratch. This is for a class assignment that I have been working on for about a week - very new to lists. We can not use generics so trying to research my question below has not been fruitful. Although I did get to watch 7 tutorials on youtube by BetterCoder and I found some stuff in my book but nothing with an example of "merging".
I have three classes - my node, my list, and my program. In my list class, I am working on building a Merge() method which eventually will compare the data in the two lists and merge them into an ordered list.
Right now for some reason my Merge method - which is very basic to help me understand what is happening - is not working correctly. It has both lists passed to it, and is adding the data from listTwo to listOne BUT for some reason when it's printing to the console the second Node's Data shows twice :
EX: 1 -> 2 -> 2
instead of printing the head (1), the next (2) and then the next (3) which it should be.
EX: 1 -> 2 -> 3
In the program class I have proven with a write line that (listOne.firstNode.Next.Next.Data) = 3 . Which it should be.
Can someone help me figure out if the nodes in list one aren't pointing to each other correctly or whatever is going on?
My Merge Method must be passed both list objects (listOne and listTwo) and eventually I need to make those passed as references but I haven't figured that out quite yet and will focus on that later I suppose.
namespace LinkedList
{
//This is my Node Class
class Node
{
public object Data { get; set; }
public Node Next { get; set; }
public Node(object dataValue) : this(dataValue, null) { }
public Node(object dataValue, Node nextNode)
{
Data = dataValue;
Next = nextNode;
}
}
//This is my List Class
class List
{
public Node firstNode;
public int count;
public List()
{
firstNode = null;
}
public bool Empty
{
get { return this.count == 0; }
}
public int Count
{
get { return this.count; }
}
public object Add(int index, object o)
{
if (index < 0)
throw new ArgumentOutOfRangeException("Index: " + index);
if (index > count)
index = count;
Node current = this.firstNode;
if (this.Empty || index == 0)
{
this.firstNode = new Node(o, this.firstNode);
}
else
{
for (int i = 0; i < index - 1; i++)
current = current.Next;
current.Next = new Node(o, current.Next);
}
count++;
return o;
}
public object Add(object o)
{
return this.Add(count, o);
}
public object Merge(List a, List b)
{
a.Add(b.firstNode.Data);
return a;
}
public void Print()
{
while (this.count > 0)
{
Console.Write(firstNode.Data + "->");
if(firstNode.Next != null)
firstNode.Data = firstNode.Next.Data;
count--;
}
}
}
//And here is my Program
class Program
{
static void Main(string[] args)
{
List listOne = new List();
List listTwo = new List();
listOne.Add(1);
listOne.Add(2);
listTwo.Add(3);
listTwo.Print();
Console.WriteLine("");
listOne.Merge(listOne, listTwo);
Console.WriteLine("");
listOne.Print();
//This line below shows that the data "3" from listTwo is being added to listOne in the list Merge Method
//Console.WriteLine(listOne.firstNode.Next.Next.Data);
Console.ReadKey();
}
}
}
Actual problem in your print method
public void Print()
{
Node node = firstNode;
for (int i = 0; i < this.count; i++)
{
Console.Write(node.Data + "->");
if (node.Next != null)
node = node.Next;
}
}
Alex Sikilinda , you are right the merge method is incomplete.
public object Merge(List a, List b)
{
Node bNode = b.firstNode;
while (bNode != null)
{
a.Add(bNode.Data);
bNode = bNode.Next;
}
return a;
}
I would write it this way:
public void Merge(List b)
{
Node lastNode = GetLastNode();
if (lastNode != null)
{
lastNode.Next = b.firstNode;
}
else
{
this.firstNode = b.firstNode;
}
}
// this method is used to find the last node in current list
private Node GetLastNode()
{
if (this.firstNode == null)
{
return null;
}
Node current = this.firstNode;
while (current.Next != null)
{
current = current.Next;
}
return current;
}
First of all, I changed signature of Merge from public object Merge(List a, List b) to public void Merge(List b). Now we can use it like this:
listOne.Merge(listTwo);
This will link listOne's last element with the first element of listTwo and thus they are merged.
Now we need to change Print method since current version modifies the list, which shouldn't happen:
public void Print()
{
Node currentNode = this.firstNode;
while(currentNode != null)
{
Console.Write(currentNode.Data + ' ');
currentNode = currentNode.Next;
}
}
Instead of assigning the data back to first node I assign the
firstNode = firstNode.Next;
Please check the below Print Code
public void Print()
{
while (this.count > 0)
{
Console.Write(firstNode.Data + "->");
if (firstNode.Next != null)
firstNode = firstNode.Next;
count--;
}
}
I'm currently doing some unit testing of a copy function and I need to compare the elements of the objects between the old list, and the newly copied list.
It works fine, but I was wondering if I can do it in a way that doesn't involve a for loop.
Here is my object:
new NaturePointObject
{
SId = 1,
Name = "Test",
Category = NaturePointCategory.Category1,
CreatorType = CreatorTypeEnum.1,
NaturR = NaturR.Bn,
Description = "Test",
Kumulation = Kumulation.EnEjendom,
Id = 1
}
My old list contains "NaturePointObject" and is called naturPointList, and it will be copied to a list called newNaturePointList.
Here is how I Assert to know if it copied succesfully:
Assert.AreEqual(naturPointList.Count,newNaturePointList.Count);
for (var i = 0; i < newNatureList.Count; i++)
{
Assert.AreEqual(naturPointList[i].Category, newNaturePointList[i].Category);
Assert.AreEqual(naturPointList[i].Description, newNaturePointList[i].Description);
Assert.AreEqual(naturPointList[i].Kumulation, newNaturePointList[i].Kumulation);
Assert.AreEqual(naturPointList[i].Name, newNaturePointList[i].Name);
Assert.AreEqual(naturPointList[i].CreatorType, newNaturePointList[i].CreatorType);
Assert.AreEqual(naturPointList[i].NaturR, newNaturePointList[i].NaturR);
Assert.AreNotEqual(naturPointList[i].SId, newNaturePointList[i].SId);
}
As you can see not all elements of the object must be equal. And I don't care about the "Id" of the object.
Is there a shorter way to do this, than run a for loop?
Probably you want to use CollectionAssert:
CollectionAssert.AreEqual(naturPointList, newNaturePointList, NaturePointObject.CategoryCreatorTypeComparer);
The only thing you need to take in mind is that you need to implement IComparer, to use in the Assert method:
public class NaturePointObject
{
private static readonly Comparer<NaturePointObject> CategoryCreatorTypeComparerInstance = new CategoryCreatorTypeRelationalComparer();
private sealed class CategoryCreatorTypeRelationalComparer : Comparer<NaturePointObject>
{
public override int Compare(NaturePointObject x, NaturePointObject y)
{
// compare fields which makes sense
if (ReferenceEquals(x, y)) return 0;
if (ReferenceEquals(null, y)) return 1;
if (ReferenceEquals(null, x)) return -1;
var categoryComparison = string.Compare(x.Category, y.Category, StringComparison.Ordinal);
if (categoryComparison != 0) return categoryComparison;
return string.Compare(x.CreatorType, y.CreatorType, StringComparison.Ordinal);
}
}
public static Comparer<NaturePointObject> CategoryCreatorTypeComparer
{
get
{
return CategoryCreatorTypeComparerInstance;
}
}
public int SId { get; set; }
public string Category { get; set; }
//other properties
public string CreatorType { get; set; }
}
You can try
Assert.IsTrue(naturPointList.SequenceEqual(newNaturePointList));
If you want to ignore the Id, you can create other classes (without Ids).
Later edit: you could overwrite the Equals method and ignore the Id.
Is there a collection in C# that guarantees me that I will have only unique elements? I've read about HashSet, but this collection can contain duplicates. Here is my code:
public class Bean
{
public string Name { get; set; }
public int Id { get; set; }
public override bool Equals(object obj)
{
var bean = obj as Bean;
if (bean == null)
{
return false;
}
return this.Name.Equals(bean.Name) && this.Id == bean.Id;
}
public override int GetHashCode()
{
return Name.GetHashCode() * this.Id.GetHashCode();
}
}
You may complain about using non-readonly properties in my GetHashCode method, but this is a way of doing (not the right one).
HashSet<Bean> set = new HashSet<Bean>();
Bean b1 = new Bean {Name = "n", Id = 1};
Bean b2 = new Bean {Name = "n", Id = 2};
set.Add(b1);
set.Add(b2);
b2.Id = 1;
var elements = set.ToList();
var elem1 = elements[0];
var elem2 = elements[1];
if (elem1.Equals(elem2))
{
Console.WriteLine("elements are equal");
}
And in this case, my set contains duplicates.
So is there a collection in C# that guarantees me that it does not contains duplicates?
So is there a collection in C# that guarantees me that it does not
contains duplicates?
There is no existing collection class in C# that does this. You could write your own, but there is no existing one.
Some extra information regarding the issue you are experiencing
If you change a HashSet entry after adding it to the HashSet, then you need to regenerate the HashSet. My below RegenerateHashSet can be used to do that.
The reason you need to regenerate is that duplicate detection only occurs at insertion time (or, in other words, it relies on you not changing an object after you insert it). Which makes sense, if you think about it. The HashSet has no way to detect that an object it contains has changed.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
public static class HashSetExtensions
{
public static HashSet<T> RegenerateHashSet<T>(this HashSet<T> original)
{
return new HashSet<T>(original, original.Comparer);
}
}
public class Bean
{
public string Name { get; set; }
public int Id { get; set; }
public override bool Equals(object obj)
{
var bean = obj as Bean;
if (bean == null)
{
return false;
}
return Name.Equals(bean.Name) && Id == bean.Id;
}
public override int GetHashCode()
{
return Name.GetHashCode() * Id.GetHashCode();
}
}
public class Program
{
static void Main(string[] args)
{
HashSet<Bean> set = new HashSet<Bean>();
Bean b1 = new Bean { Name = "n", Id = 1 };
Bean b2 = new Bean { Name = "n", Id = 2 };
set.Add(b1);
set.Add(b2);
b2.Id = 1;
var elements = set.ToList();
var elem1 = elements[0];
var elem2 = elements[1];
if (elem1.Equals(elem2))
{
Console.WriteLine("elements are equal");
}
Console.WriteLine(set.Count);
set = set.RegenerateHashSet();
Console.WriteLine(set.Count);
Console.ReadLine();
}
}
}
Note that the above technique is not bullet-proof - if you add two objects (Object A and Object B) which are duplicates and then change Object B to be different to Object A then the HashSet will still only have one entry in it (since Object B was never added). As such, what you probably want to do is actually store your complete list in a List instead, and then use new HashSet<T>(yourList) whenever you want unique entries. The below class may assist you if you decide to go down that route.
public class RecalculatingHashSet<T>
{
private List<T> originalValues = new List<T>();
public HashSet<T> GetUnique()
{
return new HashSet<T>(originalValues);
}
public void Add(T item)
{
originalValues.Add(item);
}
}
If you don't write your own collection type and handle property changed events to re-evaluate the items, you need to re-evaluate the items at each access. This can be accomplished with LINQ deferred execution:
ICollection<Bean> items= new List<Bean>();
IEnumerable<Bean> reader = items.Distinct();
Rule: only use items to insert or remove elements, use reader for any read access.
Bean b1 = new Bean { Name = "n", Id = 1 };
Bean b2 = new Bean { Name = "n", Id = 2 };
items.Add(b1);
items.Add(b2);
b2.Id = 1;
var elements = reader.ToList();
var elem1 = elements[0];
var elem2 = elements[1]; // throws exception because there is only one element in the result list.
First let me show a simple test case of the problem and how to trigger it. Here is the class:
class ProtoRecurseTest
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
}
There are no protobuf annotations as that is being taken care of programmatically. I have manually ensured that the class along with Back and Children are all added to the schema with .AsReferenceDefault = true.
The recursion triggering occurs when an instance is populated to a depth of at least 8 bizarrely enough. 7 is fine. Population code is straight forward:
ProtoRecurseTest recurseTest = new ProtoRecurseTest();
ProtoRecurseTest recurseItem = recurseTest;
for (int i = 0; i < 8; i++)
recurseItem = recurseItem.Add();
And then serialize recurseTest. This behavior only occurs when the children are in a list but in a list it occurs even with only 1 child per list as you end up with from the sample populating code. When I replaced the children with a single reference everything serialized fine.
This is using ProtoBuf.NET 2.1.0.0.
Here is a solution, but one I'm not particularly fond of. It was based on #marc-gravell 's answer to this question.
class ProtoRecurseTest : ISerializationManagementCallbacks
{
private int nextPayload = 1;
public int Payload { get; private set; } = 0;
public ProtoRecurseTest Back { get; private set; } = null;
public List<ProtoRecurseTest> Children { get; set; } = new List<ProtoRecurseTest>();
public ProtoRecurseTest Add()
{
ProtoRecurseTest result = new ProtoRecurseTest(this, nextPayload++);
Children.Add(result);
return result;
}
public ProtoRecurseTest()
{
}
private ProtoRecurseTest(ProtoRecurseTest parent, int payload)
{
Back = parent;
this.Payload = payload;
nextPayload = payload + 1;
}
private static void ToStringHelper(ProtoRecurseTest proto, StringBuilder sb)
{
sb.Append(proto.Payload + " -> ");
// another little hassle of protobuf due to empty list -> null deserialization
if (proto.Children != null)
{
foreach (var child in proto.Children)
ToStringHelper(child, sb);
}
}
public override string ToString()
{
StringBuilder sb = new StringBuilder();
ToStringHelper(this, sb);
return sb.ToString();
}
static void PreSerializationHelper(ProtoRecurseTest instance)
{
instance.Back = null;
foreach (var child in instance.Children)
PreSerializationHelper(child);
}
public void BeforeSerialization()
{
PreSerializationHelper(this);
}
static void PostDeserializationHelper(ProtoRecurseTest instance, ProtoRecurseTest parent)
{
if (instance.Children == null)
instance.Children = new List<ProtoRecurseTest>();
instance.Back = parent;
foreach (var child in instance.Children)
PostDeserializationHelper(child, instance);
}
public void AfterDeserialization()
{
PostDeserializationHelper(this, null);
}
}
The calls to serialize/deserialize now simply check if the type can be casted to the ISerializationManagementCallbacks (which was just provide a BeforeSerialization and AfterDeserialization method) before doing their business and then calling the appropriate methods if so. And it works fine.
However, I'm not really fond of mixing ever more serialization issues into my codebase - that's actually why the schema is generated programmatically. Ideally I'd like to separate the serialization entirely but the empty list -> null issue (which not to say it's an "issue" per se but just an undesirale part of the protobuf standard) already makes that impossible, but this would be another even more esoteric issue to work around and I do think this one might indeed be an issue.