I am looking for a C# specific , open source (or source code available) implementation of recursive, or deep, object comparison.
I currently have two graphs of live objects that I am looking to compare to each other, with the result of the comparison being a set of discrepancies in the graphs. The objects are instantiations of a set of classes that are known at run time (but not necessarily at compile time).
There is a specific requirement to be able to map from the discrepancies in the graphs, back to the objects containing the discrepancies.
I found a really nice, free implementation at www.kellermansoftware.com called Compare .NET Objects which can be found here. Highly recommended.
Appears to have relocated to github - most recent version is available here
This is a complex area; I've done some things like this at runtime, and it quickly gets messy. If possible, you might find that the simplest way to do this is to serialize the objects and compare the serialized form (perhaps xml-diff and XmlSerializer). This is complicated a little by the types not being known until runtime, but not hugely (you can always use new XmlSerializer(obj.GetType()) etc).
That would be my default approach, anyway.
Here's an NUnit 2.4.6 custom constraint we use for comparing complex graphs. It supports embedded collections, parent references, setting tolerance for numeric comparisons, identifying field names to ignore (even deep within the hierarchy), and decorating types to be always ignored.
I'm sure this code can be adapted to be used outside NUnit, the bulk of the code isn't dependent on NUnit.
We use this in thousands of unit tests.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using NUnit.Framework;
using NUnit.Framework.Constraints;
namespace Tests
{
public class ContentsEqualConstraint : Constraint
{
private readonly object expected;
private Constraint failedEquality;
private string expectedDescription;
private string actualDescription;
private readonly Stack<string> typePath = new Stack<string>();
private string typePathExpanded;
private readonly HashSet<string> _ignoredNames = new HashSet<string>();
private readonly HashSet<Type> _ignoredTypes = new HashSet<Type>();
private readonly LinkedList<Type> _ignoredInterfaces = new LinkedList<Type>();
private readonly LinkedList<string> _ignoredSuffixes = new LinkedList<string>();
private readonly IDictionary<Type, Func<object, object, bool>> _predicates = new Dictionary<Type, Func<object, object, bool>>();
private bool _withoutSort;
private int _maxRecursion = int.MaxValue;
private readonly HashSet<VisitedComparison> _visitedObjects = new HashSet<VisitedComparison>();
private static readonly HashSet<string> _globallyIgnoredNames = new HashSet<string>();
private static readonly HashSet<Type> _globallyIgnoredTypes = new HashSet<Type>();
private static readonly LinkedList<Type> _globallyIgnoredInterfaces = new LinkedList<Type>();
private static object _regionalTolerance;
public ContentsEqualConstraint(object expectedValue)
{
expected = expectedValue;
}
public ContentsEqualConstraint Comparing<T>(Func<T, T, bool> predicate)
{
Type t = typeof (T);
if (predicate == null)
{
_predicates.Remove(t);
}
else
{
_predicates[t] = (x, y) => predicate((T) x, (T) y);
}
return this;
}
public ContentsEqualConstraint Ignoring(string fieldName)
{
_ignoredNames.Add(fieldName);
return this;
}
public ContentsEqualConstraint Ignoring(Type fieldType)
{
if (fieldType.IsInterface)
{
_ignoredInterfaces.AddFirst(fieldType);
}
else
{
_ignoredTypes.Add(fieldType);
}
return this;
}
public ContentsEqualConstraint IgnoringSuffix(string suffix)
{
if (string.IsNullOrEmpty(suffix))
{
throw new ArgumentNullException("suffix");
}
_ignoredSuffixes.AddLast(suffix);
return this;
}
public ContentsEqualConstraint WithoutSort()
{
_withoutSort = true;
return this;
}
public ContentsEqualConstraint RecursingOnly(int levels)
{
_maxRecursion = levels;
return this;
}
public static void GlobalIgnore(string fieldName)
{
_globallyIgnoredNames.Add(fieldName);
}
public static void GlobalIgnore(Type fieldType)
{
if (fieldType.IsInterface)
{
_globallyIgnoredInterfaces.AddFirst(fieldType);
}
else
{
_globallyIgnoredTypes.Add(fieldType);
}
}
public static IDisposable RegionalIgnore(string fieldName)
{
return new RegionalIgnoreTracker(fieldName);
}
public static IDisposable RegionalIgnore(Type fieldType)
{
return new RegionalIgnoreTracker(fieldType);
}
public static IDisposable RegionalWithin(object tolerance)
{
return new RegionalWithinTracker(tolerance);
}
public override bool Matches(object actualValue)
{
typePathExpanded = null;
actual = actualValue;
return Matches(expected, actualValue);
}
private bool Matches(object expectedValue, object actualValue)
{
bool matches = true;
if (!MatchesNull(expectedValue, actualValue, ref matches))
{
return matches;
}
// DatesEqualConstraint supports tolerance in dates but works as equal constraint for everything else
Constraint eq = new DatesEqualConstraint(expectedValue).Within(tolerance ?? _regionalTolerance);
if (eq.Matches(actualValue))
{
return true;
}
if (MatchesVisited(expectedValue, actualValue, ref matches))
{
if (MatchesDictionary(expectedValue, actualValue, ref matches) &&
MatchesList(expectedValue, actualValue, ref matches) &&
MatchesType(expectedValue, actualValue, ref matches) &&
MatchesPredicate(expectedValue, actualValue, ref matches))
{
MatchesFields(expectedValue, actualValue, eq, ref matches);
}
}
return matches;
}
private bool MatchesNull(object expectedValue, object actualValue, ref bool matches)
{
if (IsNullEquivalent(expectedValue))
{
expectedValue = null;
}
if (IsNullEquivalent(actualValue))
{
actualValue = null;
}
if (expectedValue == null && actualValue == null)
{
matches = true;
return false;
}
if (expectedValue == null)
{
expectedDescription = "null";
actualDescription = "NOT null";
matches = Failure;
return false;
}
if (actualValue == null)
{
expectedDescription = "not null";
actualDescription = "null";
matches = Failure;
return false;
}
return true;
}
private bool MatchesType(object expectedValue, object actualValue, ref bool matches)
{
Type expectedType = expectedValue.GetType();
Type actualType = actualValue.GetType();
if (expectedType != actualType)
{
try
{
Convert.ChangeType(actualValue, expectedType);
}
catch(InvalidCastException)
{
expectedDescription = expectedType.FullName;
actualDescription = actualType.FullName;
matches = Failure;
return false;
}
}
return true;
}
private bool MatchesPredicate(object expectedValue, object actualValue, ref bool matches)
{
Type t = expectedValue.GetType();
Func<object, object, bool> predicate;
if (_predicates.TryGetValue(t, out predicate))
{
matches = predicate(expectedValue, actualValue);
return false;
}
return true;
}
private bool MatchesVisited(object expectedValue, object actualValue, ref bool matches)
{
var c = new VisitedComparison(expectedValue, actualValue);
if (_visitedObjects.Contains(c))
{
matches = true;
return false;
}
_visitedObjects.Add(c);
return true;
}
private bool MatchesDictionary(object expectedValue, object actualValue, ref bool matches)
{
if (expectedValue is IDictionary && actualValue is IDictionary)
{
var expectedDictionary = (IDictionary)expectedValue;
var actualDictionary = (IDictionary)actualValue;
if (expectedDictionary.Count != actualDictionary.Count)
{
expectedDescription = expectedDictionary.Count + " item dictionary";
actualDescription = actualDictionary.Count + " item dictionary";
matches = Failure;
return false;
}
foreach (DictionaryEntry expectedEntry in expectedDictionary)
{
if (!actualDictionary.Contains(expectedEntry.Key))
{
expectedDescription = expectedEntry.Key + " exists";
actualDescription = expectedEntry.Key + " does not exist";
matches = Failure;
return false;
}
if (CanRecurseFurther)
{
typePath.Push(expectedEntry.Key.ToString());
if (!Matches(expectedEntry.Value, actualDictionary[expectedEntry.Key]))
{
matches = Failure;
return false;
}
typePath.Pop();
}
}
matches = true;
return false;
}
return true;
}
private bool MatchesList(object expectedValue, object actualValue, ref bool matches)
{
if (!(expectedValue is IList && actualValue is IList))
{
return true;
}
var expectedList = (IList) expectedValue;
var actualList = (IList) actualValue;
if (!Matches(expectedList.Count, actualList.Count))
{
matches = false;
}
else
{
if (CanRecurseFurther)
{
int max = expectedList.Count;
if (max != 0 && !_withoutSort)
{
SafeSort(expectedList);
SafeSort(actualList);
}
for (int i = 0; i < max; i++)
{
typePath.Push(i.ToString());
if (!Matches(expectedList[i], actualList[i]))
{
matches = false;
return false;
}
typePath.Pop();
}
}
matches = true;
}
return false;
}
private void MatchesFields(object expectedValue, object actualValue, Constraint equalConstraint, ref bool matches)
{
Type expectedType = expectedValue.GetType();
FieldInfo[] fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
// should have passed the EqualConstraint check
if (expectedType.IsPrimitive ||
expectedType == typeof(string) ||
expectedType == typeof(Guid) ||
fields.Length == 0)
{
failedEquality = equalConstraint;
matches = Failure;
return;
}
if (expectedType == typeof(DateTime))
{
var expectedDate = (DateTime)expectedValue;
var actualDate = (DateTime)actualValue;
if (Math.Abs((expectedDate - actualDate).TotalSeconds) > 3.0)
{
failedEquality = equalConstraint;
matches = Failure;
return;
}
matches = true;
return;
}
if (CanRecurseFurther)
{
while(true)
{
foreach (FieldInfo field in fields)
{
if (!Ignore(field))
{
typePath.Push(field.Name);
if (!Matches(GetValue(field, expectedValue), GetValue(field, actualValue)))
{
matches = Failure;
return;
}
typePath.Pop();
}
}
expectedType = expectedType.BaseType;
if (expectedType == null)
{
break;
}
fields = expectedType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
}
}
matches = true;
return;
}
private bool Ignore(FieldInfo field)
{
if (_ignoredNames.Contains(field.Name) ||
_ignoredTypes.Contains(field.FieldType) ||
_globallyIgnoredNames.Contains(field.Name) ||
_globallyIgnoredTypes.Contains(field.FieldType) ||
field.GetCustomAttributes(typeof (IgnoreContentsAttribute), false).Length != 0)
{
return true;
}
foreach(string ignoreSuffix in _ignoredSuffixes)
{
if (field.Name.EndsWith(ignoreSuffix))
{
return true;
}
}
foreach (Type ignoredInterface in _ignoredInterfaces)
{
if (ignoredInterface.IsAssignableFrom(field.FieldType))
{
return true;
}
}
return false;
}
private static bool Failure
{
get
{
return false;
}
}
private static bool IsNullEquivalent(object value)
{
return value == null ||
value == DBNull.Value ||
(value is int && (int) value == int.MinValue) ||
(value is double && (double) value == double.MinValue) ||
(value is DateTime && (DateTime) value == DateTime.MinValue) ||
(value is Guid && (Guid) value == Guid.Empty) ||
(value is IList && ((IList)value).Count == 0);
}
private static object GetValue(FieldInfo field, object source)
{
try
{
return field.GetValue(source);
}
catch(Exception ex)
{
return ex;
}
}
public override void WriteMessageTo(MessageWriter writer)
{
if (TypePath.Length != 0)
{
writer.WriteLine("Failure on " + TypePath);
}
if (failedEquality != null)
{
failedEquality.WriteMessageTo(writer);
}
else
{
base.WriteMessageTo(writer);
}
}
public override void WriteDescriptionTo(MessageWriter writer)
{
writer.Write(expectedDescription);
}
public override void WriteActualValueTo(MessageWriter writer)
{
writer.Write(actualDescription);
}
private string TypePath
{
get
{
if (typePathExpanded == null)
{
string[] p = typePath.ToArray();
Array.Reverse(p);
var text = new StringBuilder(128);
bool isFirst = true;
foreach(string part in p)
{
if (isFirst)
{
text.Append(part);
isFirst = false;
}
else
{
int i;
if (int.TryParse(part, out i))
{
text.Append("[" + part + "]");
}
else
{
text.Append("." + part);
}
}
}
typePathExpanded = text.ToString();
}
return typePathExpanded;
}
}
private bool CanRecurseFurther
{
get
{
return typePath.Count < _maxRecursion;
}
}
private static bool SafeSort(IList list)
{
if (list == null)
{
return false;
}
if (list.Count < 2)
{
return true;
}
try
{
object first = FirstNonNull(list) as IComparable;
if (first == null)
{
return false;
}
if (list is Array)
{
Array.Sort((Array)list);
return true;
}
return CallIfExists(list, "Sort");
}
catch
{
return false;
}
}
private static object FirstNonNull(IEnumerable enumerable)
{
if (enumerable == null)
{
throw new ArgumentNullException("enumerable");
}
foreach (object item in enumerable)
{
if (item != null)
{
return item;
}
}
return null;
}
private static bool CallIfExists(object instance, string method)
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
if (String.IsNullOrEmpty(method))
{
throw new ArgumentNullException("method");
}
Type target = instance.GetType();
MethodInfo m = target.GetMethod(method, new Type[0]);
if (m != null)
{
m.Invoke(instance, null);
return true;
}
return false;
}
#region VisitedComparison Helper
private class VisitedComparison
{
private readonly object _expected;
private readonly object _actual;
public VisitedComparison(object expected, object actual)
{
_expected = expected;
_actual = actual;
}
public override int GetHashCode()
{
return GetHashCode(_expected) ^ GetHashCode(_actual);
}
private static int GetHashCode(object o)
{
if (o == null)
{
return 0;
}
return o.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
if (obj.GetType() != typeof(VisitedComparison))
{
return false;
}
var other = (VisitedComparison) obj;
return _expected == other._expected &&
_actual == other._actual;
}
}
#endregion
#region RegionalIgnoreTracker Helper
private class RegionalIgnoreTracker : IDisposable
{
private readonly string _fieldName;
private readonly Type _fieldType;
public RegionalIgnoreTracker(string fieldName)
{
if (!_globallyIgnoredNames.Add(fieldName))
{
_globallyIgnoredNames.Add(fieldName);
_fieldName = fieldName;
}
}
public RegionalIgnoreTracker(Type fieldType)
{
if (!_globallyIgnoredTypes.Add(fieldType))
{
_globallyIgnoredTypes.Add(fieldType);
_fieldType = fieldType;
}
}
public void Dispose()
{
if (_fieldName != null)
{
_globallyIgnoredNames.Remove(_fieldName);
}
if (_fieldType != null)
{
_globallyIgnoredTypes.Remove(_fieldType);
}
}
}
#endregion
#region RegionalWithinTracker Helper
private class RegionalWithinTracker : IDisposable
{
public RegionalWithinTracker(object tolerance)
{
_regionalTolerance = tolerance;
}
public void Dispose()
{
_regionalTolerance = null;
}
}
#endregion
#region IgnoreContentsAttribute
[AttributeUsage(AttributeTargets.Field)]
public sealed class IgnoreContentsAttribute : Attribute
{
}
#endregion
}
public class DatesEqualConstraint : EqualConstraint
{
private readonly object _expected;
public DatesEqualConstraint(object expectedValue) : base(expectedValue)
{
_expected = expectedValue;
}
public override bool Matches(object actualValue)
{
if (tolerance != null && tolerance is TimeSpan)
{
if (_expected is DateTime && actualValue is DateTime)
{
var expectedDate = (DateTime) _expected;
var actualDate = (DateTime) actualValue;
var toleranceSpan = (TimeSpan) tolerance;
if ((actualDate - expectedDate).Duration() <= toleranceSpan)
{
return true;
}
}
tolerance = null;
}
return base.Matches(actualValue);
}
}
}
This is actually a simple process. Using reflection you can compare each field on the object.
public static Boolean ObjectMatches(Object x, Object y)
{
if (x == null && y == null)
return true;
else if ((x == null && y != null) || (x != null && y == null))
return false;
Type tx = x.GetType();
Type ty = y.GetType();
if (tx != ty)
return false;
foreach(FieldInfo field in tx.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
if (field.FieldType.IsValueType && (field.GetValue(x).ToString() != field.GetValue(y).ToString()))
return false;
else if (field.FieldType.IsClass && !ObjectMatches(field.GetValue(x), field.GetValue(y)))
return false;
}
return true;
}
Using the Nuget suggested by Jesse and this code I managed to compare two objects with great results.
using KellermanSoftware.CompareNetObjects;
using System;
namespace MyProgram.UnitTestHelper
{
public class ObjectComparer
{
public static bool ObjectsHaveSameValues(object first, object second)
{
CompareLogic cl = new CompareLogic();
ComparisonResult result = cl.Compare(first, second);
if (!result.AreEqual)
Console.WriteLine(result.DifferencesString);
return result.AreEqual;
}
}
}
Here is a simple comparer that we've used with unit testing to assert that two objects have equal properties. It's a mash-up of ideas found in various articles, and handles circular references.
public static class TestHelper
{
public static void AssertAreEqual(Object expected, Object actual, String name)
{
// Start a new check with an empty list of actual objects checked
// The list of actual objects checked is used to ensure that circular references don't result in infinite recursion
List<Object> actualObjectsChecked = new List<Object>();
AssertAreEqual(expected, actual, name, actualObjectsChecked);
}
private static void AssertAreEqual(Object expected, Object actual, String name, List<Object> actualObjectsChecked)
{
// Just return if already checked the actual object
if (actualObjectsChecked.Contains(actual))
{
return;
}
actualObjectsChecked.Add(actual);
// If both expected and actual are null, they are considered equal
if (expected == null && actual == null)
{
return;
}
if (expected == null && actual != null)
{
Assert.Fail(String.Format("The actual value of {0} was not null when null was expected.", name));
}
if (expected != null && actual == null)
{
Assert.Fail(String.Format("The actual value of {0} was null when an instance was expected.", name));
}
// Get / check type info
// Note: GetType always returns instantiated (i.e. most derived) type
Type expectedType = expected.GetType();
Type actualType = actual.GetType();
if (expectedType != actualType)
{
Assert.Fail(String.Format("The actual type of {0} was not the same as the expected type.", name));
}
// If expected is a Primitive, Value, or IEquatable type, assume Equals is sufficient to check
// Note: Every IEquatable type should have also overridden Object.Equals
if (expectedType.IsPrimitive || expectedType.IsValueType || expectedType.IsIEquatable())
{
Assert.IsTrue(expected.Equals(actual), "The actual {0} is not equal to the expected.", name);
return;
}
// If expected is an IEnumerable type, assume comparing enumerated items is sufficient to check
IEnumerable<Object> expectedEnumerable = expected as IEnumerable<Object>;
IEnumerable<Object> actualEnumerable = actual as IEnumerable<Object>;
if ((expectedEnumerable != null) && (actualEnumerable != null))
{
Int32 actualEnumerableCount = actualEnumerable.Count();
Int32 expectedEnumerableCount = expectedEnumerable.Count();
// Check size first
if (actualEnumerableCount != expectedEnumerableCount)
{
Assert.Fail(String.Format("The actual number of enumerable items in {0} did not match the expected number.", name));
}
// Check items in order, assuming order is the same
for (int i = 0; i < actualEnumerableCount; ++i)
{
AssertAreEqual(expectedEnumerable.ElementAt(i), actualEnumerable.ElementAt(i), String.Format("{0}[{1}]", name, i), actualObjectsChecked);
}
return;
}
// If expected is not a Primitive, Value, IEquatable, or Ienumerable type, assume comparing properties is sufficient to check
// Iterate through properties
foreach (PropertyInfo propertyInfo in actualType.GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
// Skip properties that can't be read or require parameters
if ((!propertyInfo.CanRead) || (propertyInfo.GetIndexParameters().Length != 0))
{
continue;
}
// Get properties from both
Object actualProperty = propertyInfo.GetValue(actual, null);
Object expectedProperty = propertyInfo.GetValue(expected, null);
AssertAreEqual(expectedProperty, actualProperty, String.Format("{0}.{1}", name, propertyInfo.Name), actualObjectsChecked);
}
}
public static Boolean IsIEquatable(this Type type)
{
return type.GetInterfaces().Any(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IEquatable<>));
}
}
Related
I have an error with my script, Unity tells me this:
Library\PackageCache\com.unity.package-manager-ui#1.9.11\Editor\Sources\UI\Common\PopupField.cs(118,33): error CS0115: 'PopupField.ExecuteDefaultAction(EventBase)': no suitable method found to override
and this is the script:(I'm a noob with scripts)
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.UIElements;
namespace UnityEditor.PackageManager.UI
{
internal class PopupField<T> : BaseTextElement, INotifyValueChanged<T>
{
private readonly List<T> m_PossibleValues;
private Func<T, string> m_Callback;
private EventCallback<ChangeEvent<T>> m_valueCallback;
private T m_Value;
public T value
{
get { return m_Value; }
set
{
if (EqualityComparer<T>.Default.Equals(m_Value, value))
return;
if (!m_PossibleValues.Contains(value))
throw new ArgumentException(string.Format("Value {0} is not present in the list of possible values", value));
m_Value = value;
m_Index = m_PossibleValues.IndexOf(m_Value);
if (m_Callback != null)
text = m_Callback(m_Value);
else
text = m_Value.ToString();
#if UNITY_2018_3_OR_NEWER
MarkDirtyRepaint();
#else
Dirty(ChangeType.Repaint);
#endif
}
}
private int m_Index = -1;
public int index
{
get { return m_Index; }
set
{
if (value != m_Index)
{
if (value >= m_PossibleValues.Count || value < 0)
throw new ArgumentException(string.Format("Index {0} is beyond the scope of possible value", value));
m_Index = value;
this.value = m_PossibleValues[m_Index];
}
}
}
/// <summary>
/// Callback that will return the string to be set in the field's label.
/// </summary>
/// <param name="callback"></param>
public void SetLabelCallback(Func<T, string> callback)
{
m_Callback = callback;
}
private PopupField(List<T> possibleValues)
{
if (possibleValues == null)
throw new ArgumentNullException("possibleValues can't be null");
m_PossibleValues = possibleValues;
AddToClassList("popupField");
}
public PopupField(List<T> possibleValues, T defaultValue) :
this(possibleValues)
{
if (defaultValue == null)
throw new ArgumentNullException("defaultValue can't be null");
if (!m_PossibleValues.Contains(defaultValue))
throw new ArgumentException(string.Format("Default value {0} is not present in the list of possible values", defaultValue));
// note: idx will be set when setting value
value = defaultValue;
}
public PopupField(List<T> possibleValues, int defaultIndex) :
this(possibleValues)
{
if (defaultIndex >= m_PossibleValues.Count || defaultIndex < 0)
throw new ArgumentException(string.Format("Default Index {0} is beyond the scope of possible value", value));
// note: value will be set when setting idx
index = defaultIndex;
}
public void SetValueAndNotify(T newValue)
{
if (!EqualityComparer<T>.Default.Equals(newValue, value))
{
using (ChangeEvent<T> evt = ChangeEvent<T>.GetPooled(value, newValue))
{
value = newValue;
if (m_valueCallback != null)
m_valueCallback(evt);
}
}
}
public void OnValueChanged(EventCallback<ChangeEvent<T>> callback)
{
m_valueCallback = callback;
RegisterCallback(callback);
}
protected override void ExecuteDefaultAction(EventBase evt)
{
base.ExecuteDefaultAction(evt);
if (evt.GetEventTypeId() == MouseDownEvent.TypeId())
OnMouseDown();
}
private void OnMouseDown()
{
var menu = new GenericMenu();
foreach (T item in m_PossibleValues)
{
bool isSelected = EqualityComparer<T>.Default.Equals(item, value);
menu.AddItem(new GUIContent(item.ToString()), isSelected,
() => ChangeValueFromMenu(item));
}
var menuPosition = new Vector2(0.0f, layout.height);
menuPosition = this.LocalToWorld(menuPosition);
var menuRect = new Rect(menuPosition, Vector2.zero);
menu.DropDown(menuRect);
}
private void ChangeValueFromMenu(T menuItem)
{
SetValueAndNotify(menuItem);
}
}
}
See BaseTextElement
Experimental: this API is experimental and might be changed or removed in the future.
apparently it was ... That's why it is called Experimental
There currently is a PopupFied<T> already but again
Experimental: this API is experimental and might be changed or removed in the future.
Looks like a version issue. Check out the following link:
https://forum.unity.com/threads/unity-ui-elements-could-not-be-found-in-2018-3-0a6-beta-personal.550672/
I downloaded this asset of asset store and after it has to be downloaded into my project i got two errors:
#1.9.11/Editor/Sources/UI/Common/PopupField.cs(8,36): error CS0246: The type or namespace name `BaseTextElement' could not be found. Are you missing an assembly reference?
PopupField.cs(118,33): error CS0115: `UnityEditor.PackageManager.UI.PopupField.ExecuteDefaultAction(UnityEngine.Experimental.UIElements.EventBase)' is marked as an override but no suitable method found to override
I have tried looking up online to the solutions and even checked this website, but found nothing helpful.
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Experimental.UIElements;
namespace UnityEditor.PackageManager.UI
{
internal class PopupField<T> : BaseTextElement, INotifyValueChanged<T>
{
private readonly List<T> m_PossibleValues;
private Func<T, string> m_Callback;
private EventCallback<ChangeEvent<T>> m_valueCallback;
private T m_Value;
public T value
{
get { return m_Value; }
set
{
if (EqualityComparer<T>.Default.Equals(m_Value, value))
return;
if (!m_PossibleValues.Contains(value))
throw new ArgumentException(string.Format("Value {0} is not present in the list of possible values", value));
m_Value = value;
m_Index = m_PossibleValues.IndexOf(m_Value);
if (m_Callback != null)
text = m_Callback(m_Value);
else
text = m_Value.ToString();
#if UNITY_2018_3_OR_NEWER
MarkDirtyRepaint();
#else
Dirty(ChangeType.Repaint);
#endif
}
}
private int m_Index = -1;
public int index
{
get { return m_Index; }
set
{
if (value != m_Index)
{
if (value >= m_PossibleValues.Count || value < 0)
throw new ArgumentException(string.Format("Index {0}
is beyond the scope of possible value", value));
m_Index = value;
this.value = m_PossibleValues[m_Index];
}
}
}
/// <summary>
/// Callback that will return the string to be set in the field's
label.
/// </summary>
/// <param name="callback"></param>
public void SetLabelCallback(Func<T, string> callback)
{
m_Callback = callback;
}
private PopupField(List<T> possibleValues)
{
if (possibleValues == null)
throw new ArgumentNullException("possibleValues can't be
null");
m_PossibleValues = possibleValues;
AddToClassList("popupField");
}
public PopupField(List<T> possibleValues, T defaultValue) :
this(possibleValues)
{
if (defaultValue == null)
throw new ArgumentNullException("defaultValue can't be
null");
if (!m_PossibleValues.Contains(defaultValue))
throw new ArgumentException(string.Format("Default value {0}
is not present in the list of possible values", defaultValue));
// note: idx will be set when setting value
value = defaultValue;
}
public PopupField(List<T> possibleValues, int defaultIndex) :
this(possibleValues)
{
if (defaultIndex >= m_PossibleValues.Count || defaultIndex < 0)
throw new ArgumentException(string.Format("Default Index {0}
is beyond the scope of possible value", value));
// note: value will be set when setting idx
index = defaultIndex;
}
public void SetValueAndNotify(T newValue)
{
if (!EqualityComparer<T>.Default.Equals(newValue, value))
{
using (ChangeEvent<T> evt = ChangeEvent<T>.GetPooled(value,
newValue))
{
value = newValue;
if (m_valueCallback != null)
m_valueCallback(evt);
}
}
}
public void OnValueChanged(EventCallback<ChangeEvent<T>> callback)
{
m_valueCallback = callback;
RegisterCallback(callback);
}
protected override void ExecuteDefaultAction(EventBase evt)
{
base.ExecuteDefaultAction(evt);
if (evt.GetEventTypeId() == MouseDownEvent.TypeId())
OnMouseDown();
}
private void OnMouseDown()
{
var menu = new GenericMenu();
foreach (T item in m_PossibleValues)
{
bool isSelected = EqualityComparer<T>.Default.Equals(item,
value);
menu.AddItem(new GUIContent(item.ToString()), isSelected,
() => ChangeValueFromMenu(item));
}
var menuPosition = new Vector2(0.0f, layout.height);
menuPosition = this.LocalToWorld(menuPosition);
var menuRect = new Rect(menuPosition, Vector2.zero);
menu.DropDown(menuRect);
}
private void ChangeValueFromMenu(T menuItem)
{
SetValueAndNotify(menuItem);
}
}
}`
I have a base class called Part and derived classes like Wire or Connector and many more that inherit from Part.
Now I want to implement a search function that searches all Properties of the derived classes for a string.
If necessary that string should be tried to be converted to the type of the Property. The Properties can also be Lists and should be searched on the first level.
class Part
{
public int Id { get; set; }
public string Name { get; set; }
}
class Wire : Part
{
public NumberWithUnit Diameter { get; set; }
public Weight Weight { get; set; }
}
class Connector : Part
{
public List<Part> ConnectedParts { get; set; }
}
I know how to generally search through the Properties of base types with Reflection like this
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is string)
{
if (string.Equals(value, searchString))
return true;
}
else if (value is int)
{
int v;
if (int.TryParse(searchString, out v))
{
if(v == (int) value)
return true;
}
}
}
return false;
}
But that would be a long list of types and I have Properties of Type Weight for instance and many more. Is there some kind of general way to search without casting all types?
Consider going the opposite direction with your conversion. Rather than converting your search string into each possible value, just convert the value into a string:
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is IEnumerable)
{
// special handling for collections
}
else if(value != null)
{
string valueString = value.ToString();
if (string.Equals(valueString, searchString))
return true;
}
}
return false;
}
Besides working pretty well for most built-in types, the only thing you have to do to get it to work for Weight, etc. is make sure they implement ToString().
Another solution would be to use TypeDescriptor:
private bool SearchProperties<T>(T part, string searchString) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var value = prop.GetValue(part);
if (value is IEnumerable)
{
// special handling for collections
}
else if(value != null)
{
object searchValue = null;
try
{
searchValue = TypeDescriptor.GetConverter(value).ConvertFromString(searchString);
} catch {}
if (searchValue != null && object.Equals(value, searchValue))
return true;
}
}
return false;
}
TypeDescriptor works well for most built-in types, but requires extra work if you're dealing with custom types.
I think the following should cover the most of the practical scenarios:
public static bool SearchProperties(object target, string searchString)
{
if (target == null) return false;
// Common types
var convertible = target as IConvertible;
if (convertible != null)
{
var typeCode = convertible.GetTypeCode();
if (typeCode == TypeCode.String) return target.ToString() == searchString;
if (typeCode == TypeCode.DBNull) return false;
if (typeCode != TypeCode.Object)
{
try
{
var value = Convert.ChangeType(searchString, typeCode);
return target.Equals(value);
}
catch { return false; }
}
}
if (target is DateTimeOffset)
{
DateTimeOffset value;
return DateTimeOffset.TryParse(searchString, out value) && value == (DateTimeOffset)target;
}
var enumerable = target as IEnumerable;
if (enumerable != null)
{
// Collection
foreach (var item in enumerable)
if (SearchProperties(item, searchString)) return true;
}
else
{
// Complex type
var properties = target.GetType().GetProperties();
foreach (var property in properties)
{
if (property.GetMethod == null || property.GetMethod.GetParameters().Length > 0) continue;
var value = property.GetValue(target);
if (SearchProperties(value, searchString)) return true;
}
}
return false;
}
I will give you one different idea to do it.
You could try something like that:
private bool SearchProperties<T, W>(T part, W searchValue) where T : Part
{
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
if (typeof(W) == prop.PropertyType)
{
var value = prop.GetValue(part, null);
if (searchValue.Equals(value))
return true;
}
}
return false;
}
You need to call the method like this:
private void button12_Click(object sender, EventArgs e)
{
Part p = new Part();
p.Id = 2;
p.Name = "test";
p.bla = new Bla();
SearchProperties<Part, int>(p, 2);
}
And if you need to compare the complex properties (Weight, ...) by a different way from GetHashCode you could override the method Equals or the == operator.
class Weight
{
public int Id { get; set; }
public override bool Equals(object obj)
{
return Id == ((Weight)obj).Id;
}
}
I have a class named Configurationwhich inherits from DynamicObject. It it a Dictionary<string, object>. In the constructor, it reads a text file and loads all the values from it splitted by =, so you can create dynamic configuration objects like this:
dynamic configuration = new Configuration("some_file.ini");
Here is my full class:
public sealed class Configuration : DynamicObject
{
private string Path { get; set; }
private Dictionary<string, object> Dictionary { get; set; }
public Configuration(string fileName)
{
this.Path = fileName;
this.Dictionary = new Dictionary<string, object>();
this.Populate();
}
~Configuration()
{
if (this.Dictionary != null)
{
this.Dictionary.Clear();
}
}
private void Populate()
{
using (StreamReader reader = new StreamReader(this.Path))
{
string line;
string currentSection = string.Empty;
while ((line = reader.ReadLine()) != null)
{
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
if (line.StartsWith("[") && line.EndsWith("]"))
{
currentSection = line.Trim('[', ']');
}
else if (line.Contains("="))
{
this.Dictionary.Add(string.Format("{0}{1}{2}",
currentSection,
(currentSection != string.Empty) ? "_" : string.Empty,
line.Split('=')[0].Trim()),
ParseValue(line.Split('=')[1].Trim().Split(';')[0]));
}
}
}
}
private object ParseValue(string value)
{
if (string.IsNullOrWhiteSpace(value))
return string.Empty;
if (value.StartsWith("\"") && value.EndsWith("\""))
return value.Substring(1, value.Length - 1);
if (IsNumeric(value))
return Convert.ToInt32(value);
// TODO: FIXME Floating values (not to be confuse with IPAddress).
//if (IsFloating(value))
// return Convert.ToDouble(value);
if (string.Compare(value, "true", StringComparison.OrdinalIgnoreCase) == 0)
return true;
if (string.Compare(value, "false", StringComparison.OrdinalIgnoreCase) == 0)
return false;
return value;
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (this.Dictionary.ContainsKey(binder.Name))
{
if (this.Dictionary[binder.Name] == null)
{
result = null;
}
else
{
result = this.Dictionary[binder.Name];
}
return true;
}
throw new ConfigurationException(binder.Name);
}
public override string ToString()
{
string result = this.Path + " [ ";
int processed = 0;
foreach (KeyValuePair<string, object> value in this.Dictionary)
{
result += value.Key;
processed++;
if (processed < this.Dictionary.Count)
{
result += ", ";
}
}
result += " ]";
return result;
}
private static bool IsNumeric(string value)
{
foreach (char c in value)
if (!char.IsDigit(c))
return false;
return true;
}
private static bool IsFloating(string value)
{
foreach (char c in value)
if (!char.IsDigit(c) && c != '.')
return false;
return true;
}
private class NullObject { }
}
Everything is working flawlessly. I've overriden TryGetMember and I'm returning the object based on the GetMemberBinder Name property. However, I'm facing a problem.
When I execute the following code:
string s = "some_config_key";
string d = configuration.s;
It retrieves the value of the key s rather than some_config_key, meaning that it doesn't evalute the value of the variable s. Is there a workaround for this?
Thanks.
C# dynamic features are partially compiled like the property/method access. To understand this lets take an example.
dynamic myVar = new { a=1, b="test" };
myVar.a += 1;
Here the C# type system would not test for the validity of the property while compilation, only at runtime the code is executed and if the property is not found it will through you runtime error.
So in your code the property access is already complied to access the property named "s" on the dynamic object "configuration".
In C# no-way you can do this not even in dynamically typed languages like python or javascript.
You have to use like this.
dynamic configuration = getConfig("path/to/file");
var myobj = configuration as IDictionary<string,object>;
string s = "config_key";
var value = myobj[s]; // this is correct.
I need to build a cache of one of my custom class objects(security as in finance). The object of the class can be identified in multiple ways since it has multiple identifiers like PKey, Name, Long Name, or any other type of name in different language.
So the cache should be searchable on the basis of any of the identifiers i.e. multiple keys.
How can I create such a cache?
I am using C# 4.5 but I think this is a generic problem, so solutions in any way can help
One of the solutions which I think is fast and efficient is to keep different caches for different identifiers. For e.g. < Name,Pkey>, < Long Name,Pkey>, < identifier,Pkey> and a single cache with objects of type < Pkey,Object>
Are there newer and better ways to do this?
Edit: When I am accessing the cache all the keys are not known, so I
can not make a composite key
Maybe this code will be useful:
public class Test12 {
static Map<CacheKey, SecureObject> cache = new ConcurrentHashMap<CacheKey, SecureObject>();
public static void main(String[] args) {
SecureObject secureObject = new SecureObject(new CacheKey("pkey1", "name1", 1L));
cache.put(secureObject.getKey(), secureObject);
secureObject = new SecureObject(new CacheKey("pkey1", "name2", 2L));
if (cache.containsKey(secureObject.getKey())) {
System.out.println("Already in cache.");
}
}
}
class CacheKey {
String pkey;
String name;
Long longName;
CacheKey(String pkey, String name, Long longName) {
this.pkey = pkey;
this.name = name;
this.longName = longName;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey cacheKey = (CacheKey) o;
if (pkey != null && pkey.equals(cacheKey.pkey)) {
return true;
} else {
if (name != null && name.equals(cacheKey.name)) {
return true;
} else {
if (longName != null && longName.equals(cacheKey.longName)) {
return true;
} else {
return false;
}
}
}
}
#Override
public int hashCode() {
int result = 0;
if (pkey != null) {
result = pkey.hashCode();
} else if (name != null) {
result = name.hashCode();
} else if (longName != null) {
result = longName.hashCode();
}
return result;
}
}
class SecureObject {
CacheKey key;
SecureObject(CacheKey key) {
this.key = key;
}
public CacheKey getKey() {
return key;
}
public void setKey(CacheKey key) {
this.key = key;
}
}
Prints:
Already in cache.
This is not complete solution. This is direction only.