I have some classes with lots of simple properties (from a datamodel I have no control over) -- I'd like to be able to find if the new version of an object is the same as an old version, but don't want to do 20 different "IsEqual" methods (I don't really like the "IsEqual" name because it is not an analog to ==). Another wrinkle, in most of the cases I don't want it to do a deep compare, but in some cases I do want that.
I'd like something along the lines of:
//Property could be PropertyInfo if that is necessary
bool IsEqual<T>(T first, T second, List<Property> ignorableProperties=emptyList, bool recurse=false)
{
//the comparison code returning if they are equal ignoring
//the properties in the ignorableProperties list, recursing if recurse == true
//not sure how I'd handle the comparison of sub-objects in the recursive step.
}
public static bool AreEqual<T>(this T first, T second,
bool recurse = false, params string[] propertiesToSkip)
{
if (Equals(first, second)) return true;
if (first == null)
return second == null;
else if (second == null)
return false;
if (propertiesToSkip == null) propertiesToSkip = new string[] { };
var properties = from t in first.GetType().GetProperties()
where t.CanRead
select t;
foreach (var property in properties)
{
if (propertiesToSkip.Contains(property.Name)) continue;
var v1 = property.GetValue(first, null);
var v2 = property.GetValue(second, null);
if (recurse)
if (!AreEqual(v1, v2, true, propertiesToSkip))
return false;
else
continue;
if (!Equals(v1, v2)) return false;
}
return true;
}
Here's something along those lines from our code base. It uses a list of properties to compare, rather than a list of properties to ignore. Then it returns a list of which properties did not match:
public static List<PropertyInfo> CompareObjects<T>(T o1, T o2, List<PropertyInfo> props) where T : class
{
var type = typeof(T);
var mismatched = CompareObjects(type, o1, o2, props);
return mismatched;
}
public static List<PropertyInfo> CompareObjects(Type t, object o1, object o2, List<PropertyInfo> props)
{
List<PropertyInfo> mismatched = null;
foreach (PropertyInfo prop in props)
{
if (prop.GetValue(o1, null) == null && prop.GetValue(o2, null) == null) ;
else if (
prop.GetValue(o1, null) == null || prop.GetValue(o2, null) == null ||
!prop.GetValue(o1, null).Equals(prop.GetValue(o2, null)))
{
if (mismatched == null) mismatched = new List<PropertyInfo>();
mismatched.Add(prop);
}
}
return mismatched;
}
If you wanted an IsEqual method it would be a matter of returning true/false when you find mismatched properties instead.
Hope this helps!
Here's how we achieve this in the Umbraco Framework, with a base class called AbstractEquatableObject which is a modified version of Sharp Architecture's BaseObject http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fAbstractEquatableObject.cs
Implementors override GetMembersForEqualityComparison() and the base class caches the PropertyInfo objects once per Type for the application in a ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>.
I've pasted the class here, although it refers to LogHelper elsewhere in the Framework so you can remove that (or just use our Framework lib, there's other useful stuff in there).
If you want a helper for getting a PropertyInfo from an expression, to avoid magic strings all over the place (e.g. replaced with x => x.MyProperty), check out the GetPropertyInfo methods of our ExpressionHelper at http://umbraco.codeplex.com/SourceControl/changeset/view/2b4d693de19c#Source%2fLibraries%2fUmbraco.Framework%2fExpressionHelper.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Umbraco.Framework.Diagnostics;
namespace Umbraco.Framework
{
/// <summary>
/// Objects implementing <see cref="AbstractEquatableObject{T}"/> are provided with common functionality for establishing domain-specific equality
/// and a robust implementation of GetHashCode
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
public abstract class AbstractEquatableObject<T> where T : AbstractEquatableObject<T>
{
/// <summary>
/// Returns the real type in case the <see cref="object.GetType" /> method has been proxied.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
protected internal virtual Type GetNativeType()
{
// Returns the real type in case the GetType method has been proxied
// See http://groups.google.com/group/sharp-architecture/browse_thread/thread/ddd05f9baede023a for clarification
return GetType();
}
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. </returns>
public override int GetHashCode()
{
unchecked
{
// Based on an algorithm set out at http://sharp-architecture.googlecode.com/svn/trunk/src/SharpArch/SharpArch.Core/DomainModel/BaseObject.cs
var naturalIdMembers = EnsureEqualityComparisonMembersCached();
// It's possible for two objects to return the same hash code based on
// identically valued properties, even if they're of two different types,
// so we include the object's type in the hash calculation
var hashCode = GetType().GetHashCode();
if (!naturalIdMembers.Any()) return base.GetHashCode();
foreach (var value in naturalIdMembers
.Select(x => x.GetValue(this, null))
.Where(x => !ReferenceEquals(x, null)))
{
// Check if the property value is null or default (e.g. Guid.Empty)
// In which case we just want to use the base GetHashCode because we have no other way
// of determining if the instances are different
if (value.Equals(value.GetType().GetDefaultValue()))
hashCode = (hashCode * 41) ^ base.GetHashCode();
else
hashCode = (hashCode * 41) ^ value.GetHashCode();
}
return hashCode;
}
}
/// <summary>Determines whether the specified object is equal to this instance.</summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
if (ReferenceEquals(obj, null)) return false;
var incoming = obj as AbstractEquatableObject<T>;
if (ReferenceEquals(incoming, null)) return false;
if (ReferenceEquals(this, incoming)) return true;
// (APN Oct 2011) Disabled the additional check for GetNativeType().Equals(incoming.GetNativeType())
// so that we can compare RelationById with Relation using Equals however this may need reinstating
// and using IComparable instead
return CompareCustomEqualityMembers(incoming);
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
/// <remarks></remarks>
public static bool operator ==(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
{
// If both are null, or both are same instance, return true.
if (ReferenceEquals(left, right)) return true;
// If one is null, but not both, return false.
if (((object)left == null) || ((object)right == null)) return false;
return left.Equals(right);
}
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="left">The left.</param>
/// <param name="right">The right.</param>
/// <returns>The result of the operator.</returns>
/// <remarks></remarks>
public static bool operator !=(AbstractEquatableObject<T> left, AbstractEquatableObject<T> right)
{
return !(left == right);
}
/// <summary>
/// A static <see cref="ConcurrentDictionary{Type, IEnumerable{PropertyInfo}}"/> cache of natural ids for types which may implement this abstract class.
/// </summary>
protected readonly static ConcurrentDictionary<Type, IEnumerable<PropertyInfo>> EqualityComparisonMemberCache = new ConcurrentDictionary<Type, IEnumerable<PropertyInfo>>();
/// <summary>
/// Gets the natural id members.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
protected abstract IEnumerable<PropertyInfo> GetMembersForEqualityComparison();
/// <summary>
/// Ensures the natural id members are cached in the static <see cref="EqualityComparisonMemberCache"/>.
/// </summary>
/// <returns></returns>
/// <remarks></remarks>
protected internal virtual IEnumerable<PropertyInfo> EnsureEqualityComparisonMembersCached()
{
return EqualityComparisonMemberCache.GetOrAdd(GetNativeType(), x => GetMembersForEqualityComparison());
}
/// <summary>
/// Establishes if the natural id of this instance matches that of <paramref name="compareWith"/>
/// </summary>
/// <param name="compareWith">The instance with which to compare.</param>
/// <returns></returns>
/// <remarks></remarks>
protected internal virtual bool CompareCustomEqualityMembers(AbstractEquatableObject<T> compareWith)
{
// Standard input checks - if it's the same instance, or incoming is null, etc.
if (ReferenceEquals(this, compareWith)) return true;
if (ReferenceEquals(compareWith, null)) return false;
// Get the natural id spec
var naturalIdMembers = EnsureEqualityComparisonMembersCached();
// If the overriding objct hasn't specified a natural id, just return the base Equals implementation
if (!naturalIdMembers.Any()) return base.Equals(compareWith);
// We have a natural id specified, so compare the members
foreach (var naturalIdMember in naturalIdMembers)
{
try
{
// Get the property values of this instance and the incoming instance
var localValue = naturalIdMember.GetValue(this, null);
var incomingValue = naturalIdMember.GetValue(compareWith, null);
// If the property values refere to the same instance, or both refer to null, continue the loop
if (ReferenceEquals(localValue, incomingValue) || (ReferenceEquals(localValue, null) && ReferenceEquals(incomingValue, null)))
continue;
// If this property value doesn't equal the incoming value, the comparison fails so we can return straight away
if (!localValue.Equals(incomingValue)) return false;
}
catch (Exception ex)
{
// If there was an error accessing one of the properties, log it and return false
LogHelper.TraceIfEnabled<AbstractEquatableObject<T>>("Error comparing {0} to {1}: {2}",
() => GetNativeType().Name,
() => compareWith.GetNativeType().Name,
() => ex.Message);
return false;
}
}
// To get this far means we haven't had any misses, so return true
return true;
}
}
}
Related
I have two models
public class NewrecordModel
{
public string NewName { get; set; }
public string NewFileName { get; set; }
public string NewFileVersion { get; set; }
}
public class OldrecordModel
{
public string OldName { get; set; }
public string OldFileName { get; set; }
public string OldFileVersion { get; set; }
}
I need to compare the property, NewName with OldName and NewFileVersion with OldFileVersion and return the difference from NewrecordModel in a list.
I tried the below one,
var unMatchedRecord = NewrecordModel.Where(o => !OldrecordModel.Any(n => n.OldName == o.NewName) || !OldrecordModel.Any(n => n.OldFileVersion == o.NewFileVersion));
The above one is returning the unmatch NewrecordModel in a list and it is working fine, but i need to compare OldFileVersion is less than NewFileVersion and return the list now. While using OldFileVersion is less than NewFileVersion in query it is listing unrelated data as output.
Below is the query i tried to compare,
var unMatchedVersion = NewrecordModel.Where(o => OldrecordModel.Any(n => n.OldFileVersion.ToInt() < o.NewFileVersion.ToInt()));
Is the above linq is correct. How to compare the numbers in linq and return the result.
I wrote this for get properties name and value what changed. I hope this help you.
public static class CompareObject
{
/// <summary>
/// Compares the properties of two objects of the same type and returns if all properties are equal.
/// </summary>
/// <param name="objectA">The first object to compare.</param>
/// <param name="objectB">The second object to compre.</param>
/// <param name="ignoreList">A list of property names to ignore from the comparison.</param>
/// <returns><c>true</c> if all property values are equal, otherwise <c>false</c>.</returns>
public static Dictionary<string,object> AreObjectsEqual(object objectA, object objectB, params string[] ignoreList)
{
//bool result;
var list = new Dictionary<string, object>();
if (objectA != null && objectB != null)
{
Type objectType;
objectType = objectA.GetType();
//result = true; // assume by default they are equal
foreach (PropertyInfo propertyInfo in objectType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.CanRead && !ignoreList.Contains(p.Name)))
{
// if it is a primative type, value type or implements IComparable, just directly try and compare the value
if (CanDirectlyCompare(propertyInfo.PropertyType))
{
object valueA;
object valueB;
valueA = propertyInfo.GetValue(objectA, null);
valueB = propertyInfo.GetValue(objectB, null);
if (!AreValuesEqual(valueA, valueB))
{
list.Add(propertyInfo.Name, valueA);
//Console.WriteLine("Mismatch with property '{0}.{1}' found.", objectType.FullName, propertyInfo.Name);
//result = false;
}
}
}
}
//else
// result = object.Equals(objectA, objectB);
return list;
}
/// <summary>
/// Determines whether value instances of the specified type can be directly compared.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>
/// <c>true</c> if this value instances of the specified type can be directly compared; otherwise, <c>false</c>.
/// </returns>
private static bool CanDirectlyCompare(Type type)
{
return typeof(IComparable).IsAssignableFrom(type) || type.IsPrimitive || type.IsValueType;
}
/// <summary>
/// Compares two values and returns if they are the same.
/// </summary>
/// <param name="valueA">The first value to compare.</param>
/// <param name="valueB">The second value to compare.</param>
/// <returns><c>true</c> if both values match, otherwise <c>false</c>.</returns>
private static bool AreValuesEqual(object valueA, object valueB)
{
bool result;
IComparable selfValueComparer;
selfValueComparer = valueA as IComparable;
if (valueA == null && valueB != null || valueA != null && valueB == null)
result = false; // one of the values is null
else if (selfValueComparer != null && selfValueComparer.CompareTo(valueB) != 0)
result = false; // the comparison using IComparable failed
else if (!object.Equals(valueA, valueB))
result = false; // the comparison using Equals failed
else
result = true; // match
return result;
}
}
and to use
var changedColumns = CompareObject.AreObjectsEqual(NewrecordModel, OldrecordModel);
It might be an object from NewrecordModel collection, for which there are no objects in OldrecordModel collection with the same name OR any identically named object from OldrecordModel collection has a smaller OldFileVersion
Check the codes below:
var unMatchedVersion =
newrecordModel.Where(o => !oldrecordModel.Any(n => n.OldName == o.NewName) ||
oldrecordModel.Any(n => n.OldName == o.NewName &&
int.Parse(n.OldFileVersion) < int.Parse(o.NewFileVersion)));
If you're using the Client Object Model from SharePoint and access properties which haven't been initialized or already retrieved by an
Context.Load(property);
Context.ExecuteQuery();
you get for example a:
Microsoft.SharePoint.Client.PropertyOrFieldNotInitializedException
or
The collection has not been initialized. It has not been requests or
the request has not been executed.
Exception.
Is there any proper way to check before accessing these properties if they are already initialized/retrieved? Without a Try/Catch approach. I don`t like that one's.
I want to check before a Exception has been thrown and handle it.
I already checked the
IsObjectPropertyInstantiated
IsPropertyAvailable
Methods but they don't help really. IsPropertyAvaiable only checks scalar properties and won't give a result on for example Web.Lists and IsObjectPropertyInstantiated returns true for Web.Lists although Web.Lists was not initialized.
I would say your question is already contains the correct answer to some extent.
In order to determine whether client object property is loaded or not the following methods are available:
ClientObject.IsPropertyAvailable method method indicates whether
the specified scalar property has been retrieved or set
ClientObject.IsObjectPropertyInstantiated method indicates
whether the specified property of the client object is instantiated
Tests
Test case 1: load scalar property only
ctx.Load(ctx.Web, w => w.Title);
ctx.ExecuteQuery();
//Results:
ctx.Web.IsObjectPropertyInstantiated("Lists") False
ctx.Web.IsPropertyAvailable("Title") True
Test case 2: load composite property only
ctx.Load(ctx.Web, w => w.Lists);
ctx.ExecuteQuery();
//Results:
ctx.Web.IsObjectPropertyInstantiated("Lists") True
ctx.Web.IsPropertyAvailable("Title") False
Test case 3: load both scalar and composite properties
ctx.Load(ctx.Web, w=>w.Lists,w=>w.Title);
ctx.ExecuteQuery();
//Results
ctx.Web.IsObjectPropertyInstantiated("Lists") True
ctx.Web.IsPropertyAvailable("Title") True
How to dynamically determine whether client object property is loaded or not?
Since ClientObject.IsPropertyAvailable and ClientObject.IsObjectPropertyInstantiated methods expect the property name to be specified as a string value and that could lead to typos, I usually prefer the following extension method:
public static class ClientObjectExtensions
{
/// <summary>
/// Determines whether Client Object property is loaded
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="clientObject"></param>
/// <param name="property"></param>
/// <returns></returns>
public static bool IsPropertyAvailableOrInstantiated<T>(this T clientObject, Expression<Func<T, object>> property)
where T : ClientObject
{
var expression = (MemberExpression)property.Body;
var propName = expression.Member.Name;
var isCollection = typeof(ClientObjectCollection).IsAssignableFrom(property.Body.Type);
return isCollection ? clientObject.IsObjectPropertyInstantiated(propName) : clientObject.IsPropertyAvailable(propName);
}
}
Usage
using (var ctx = new ClientContext(webUri))
{
ctx.Load(ctx.Web, w => w.Lists, w => w.Title);
ctx.ExecuteQuery();
if (ctx.Web.IsPropertyAvailableOrInstantiated(w => w.Title))
{
//...
}
if (ctx.Web.IsPropertyAvailableOrInstantiated(w => w.Lists))
{
//...
}
}
The tests provided by Vadim Gremyachev only cover one half of the scenarios - where you use ctx.Load. But when you use ctx.LoadQuery the result changes:
var query = from lst in ctx.Web.Lists where lst.Title == "SomeList" select lst;
var lists = ctx.LoadQuery(query);
ctx.ExecuteQuery();
ctx.Web.IsObjectPropertyInstantiated("Lists") -> True
ctx.Web.Lists.ServerObjectIsNull -> False
ctx.Web.Lists.Count -> CollectionNotInitializedException
So once the LoadQuery has been called on a collection, you can no longer see if the collection is actually available.
Only way in this case is to detect that the exception occurs.
OK, this is getting more and more complicated especially with SharePoint Online where results of Load and Execute methods may be incomplete even without thrown exceptions. However, below is what I collected from this and other threads combined to a LoadAndExecute method that can be a subclass extension to the ClientContext class or be converted to a static extension class. For a new client object, the object and its properties are loaded in one operation but the result is checked separately for each property. For an existing client object, only missing properties are loaded in separate operations which may consume network resources unnecessarily. So the method doesn't only check which properties are not initialized but tries to retrieve the missing ones, too. In addition, there is another topic of avoiding being throttled by overriding the Execute method of the ClientContext, but that is not included here:
/// <summary>
/// An extended ClientContext to avoid getting throttled.
/// </summary>
public partial class OnlineContext : ClientContext
{
/// <inheritdoc />
public OnlineContext(string webFullUrl, int retryCount = 0, int delay = 0)
: base(webFullUrl)
{
RetryCount = retryCount;
Delay = delay;
}
/// <summary>
/// The retry count.
/// </summary>
public int RetryCount { get; set; }
/// <summary>
/// The delay between attempts in seconds.
/// </summary>
public int Delay { get; set; }
/// <summary>
/// Loads and executes the specified client object properties.
/// </summary>
/// <typeparam name="T">the object type.</typeparam>
/// <param name="clientObject">the object.</param>
/// <param name="properties">the properties.</param>
/// <returns>true if all available, false otherwise.</returns>
public bool LoadAndExecute<T>(T clientObject, params Expression<Func<T, object>>[] properties)
where T : ClientObject
{
int retryAttempts = 0;
int backoffInterval = Math.Max(Delay, 1);
bool retry;
bool available;
do
{
if (clientObject is ClientObjectCollection)
{
// Note that Server Object can be null for collections!
ClientObjectCollection coc = (ClientObjectCollection) (ClientObject) clientObject;
if (!coc.ServerObjectIsNull.HasValue || !coc.ServerObjectIsNull.Value)
{
available = coc.AreItemsAvailable;
}
else
{
available = false;
break;
}
}
else if (clientObject.ServerObjectIsNull.HasValue)
{
available = !clientObject.ServerObjectIsNull.Value;
break;
}
else
{
available = false;
}
if (!available && retryAttempts++ <= RetryCount)
{
if (retryAttempts > 1)
{
Thread.Sleep(backoffInterval * 1000);
backoffInterval *= 2;
}
Load(clientObject, properties);
ExecuteQuery();
retry = true;
}
else
{
retry = false;
}
} while (retry);
if (available)
{
if (properties != null && properties.Length > 0)
{
foreach (Expression<Func<T, object>> property in properties)
{
if (!LoadAndExecuteProperty(clientObject, property, retryAttempts > 0))
{
available = false;
}
}
}
}
return available;
}
/// <summary>
/// Loads and executes the specified client object property.
/// </summary>
/// <typeparam name="T">the object type.</typeparam>
/// <param name="clientObject">the object.</param>
/// <param name="property">the property.</param>
/// <param name="loaded">true, if the client object was already loaded and executed at least once.</param>
/// <returns>true if available, false otherwise.</returns>
private bool LoadAndExecuteProperty<T>(T clientObject, Expression<Func<T, object>> property, bool loaded = false)
where T : ClientObject
{
string propertyName;
bool isObject;
bool isCollection;
Func<T, object> func;
Expression expression = property.Body;
if (expression is MemberExpression)
{
// Member expression, check its type to select correct property test.
propertyName = ((MemberExpression) expression).Member.Name;
isObject = typeof(ClientObject).IsAssignableFrom(property.Body.Type);
isCollection = isObject
? typeof(ClientObjectCollection).IsAssignableFrom(property.Body.Type)
: false;
func = isObject ? property.Compile() : null;
}
else if (!loaded)
{
// Unary expression or alike, test by invoking its function.
propertyName = null;
isObject = false;
isCollection = false;
func = property.Compile();
}
else
{
// Unary expression and alike should be available if just loaded.
return true;
}
int retryAttempts = 0;
int backoffInterval = Math.Max(Delay, 1);
bool retry;
bool available;
do
{
if (isObject)
{
if (clientObject.IsObjectPropertyInstantiated(propertyName))
{
ClientObject co = (ClientObject) func.Invoke(clientObject);
if (isCollection)
{
ClientObjectCollection coc = (ClientObjectCollection) co;
if (!coc.ServerObjectIsNull.HasValue || !coc.ServerObjectIsNull.Value)
{
available = coc.AreItemsAvailable;
}
else
{
available = false;
break;
}
}
else if (co.ServerObjectIsNull.HasValue)
{
available = !co.ServerObjectIsNull.Value;
break;
}
else
{
available = false;
}
}
else
{
available = false;
}
}
else if (propertyName != null)
{
available = clientObject.IsPropertyAvailable(propertyName);
}
else if (func != null)
{
try
{
func.Invoke(clientObject);
available = true;
}
catch (PropertyOrFieldNotInitializedException)
{
available = false;
}
}
else
{
available = true; // ?
}
if (!available && retryAttempts++ <= RetryCount)
{
if (retryAttempts > 1)
{
Thread.Sleep(backoffInterval * 1000);
backoffInterval *= 2;
}
Load(clientObject, property);
ExecuteQuery();
retry = true;
}
else
{
retry = false;
}
} while (retry);
return available;
}
}
The idea of using a extension is great, but only works fine with lists. The extension may choose between "object" and "scalar" properties. I think than the extension will be better this way:
public static bool IsPropertyAvailableOrInstantiated<T>(this T clientObject, Expression<Func<T, object>> property)
where T : ClientObject
{
var expression = (MemberExpression)property.Body;
var propName = expression.Member.Name;
var isObject = typeof(ClientObject).IsAssignableFrom(property.Body.Type); // test with ClientObject instead of ClientObjectList
return isObject ? clientObject.IsObjectPropertyInstantiated(propName) : clientObject.IsPropertyAvailable(propName);
}
I've created a class of classes (showing one of them) with const string that I want to itarate on.
public static class HTDB_Cols
{
public sealed class Assistant
{
public const string EntryID = "entryID",
CustName = "custName",
SerialNum = "serialNum",
UserName = "userName",
Password = "password",
EndDate = "end_date",
CustID = "custID",
TmpCheck = "tmpCheck",
Isfamily = "isfamily",
Isserver = "isserver";
}
}
public static class DB
{
public static void insert(string TableName)
{
ColumnsCollection = typeof(HTDB_Cols).GetNestedTypes().Where(f => f.DeclaringType.Name.ToLower().Equals(TableName.ToLower()));
}
}
The code above shows my attempt, but even after lots of trial and error I still couldn't get it right.
I want to have a list of all columns as const collection array or list.
var dict = typeof(HTDB_Cols).GetNestedTypes()
.First(t=>String.Compare(t.Name,TableName,true)==0)
.GetFields()
.ToDictionary(f => f.Name, f => f.GetValue(null));
To get a list
var list = typeof(HTDB_Cols).GetNestedTypes()
.First(t => String.Compare(t.Name, TableName, true) == 0)
.GetFields()
.Select(f => f.GetValue(null) as string)
.ToList();
It looks like what you need is an enum:
enum Assistant
{
EntryID,
CustName,
SerialNum,
UserName,
Password,
EndDate,
CustID,
TmpCheck,
Isfamily,
Isserver
};
You can then get all of those names as strings by doing:
string[] allNames = Enum.GetNames(typeof(Assistant));
As long as it's acceptable for you to have the names of the variables be the actual values that you care about, that's a valid option. I note that they're not quite the same in your example, but it's mostly just casing. If you can deal with using the variable names as the values, or changing the variable names to be the values you need, then that's likely to be your best option.
Now, if it really is important for the variable names to be different than the values they represent, or if you need to represent values that are illegal identifiers (for example, one of your values has a space, that's no good, and they couldn't ever start with a digit, or they might just be too long to be a convenient name). If that's the case, then what you really want is an enum that's backed by a string, rather than an integer or other numeric type. That's not strictly possible in C#, but since this has come up before I actually wrote the following class which is my best attempt at creating my own string backed enum. If you really need variable names that differ from the string values they represent, this should work for you.
All of the important stuff is right at the top, most everything after Equals is just syntactic sugar.
public struct StringEnum
{
#region Code that is to be configured
//For each value to be publicly exposed add a new field.
public static readonly StringEnum Alpha = new StringEnum("Alpha Value");
public static readonly StringEnum Beta = new StringEnum("Beta Value");
public static readonly StringEnum Invalid = new StringEnum("Invalid");
public static IEnumerable<StringEnum> AllValues
{
get
{
yield return Alpha;
yield return Beta;
yield return Invalid;
//...
//add a yield return for all instances here.
//TODO refactor to use reflection so it doesn't need to be manually updated.
}
}
#endregion
private string value;
/// <summary>
/// default constructor
/// </summary>
//private Group()
//{
// //You can make this default value whatever you want. null is another option I considered
// //(if this is a class an not a struct), but you
// //shouldn't have this be anything that doesn't exist as one of the options defined at the top of
// //the page.
// value = "Invalid";
//}
/// <summary>
/// primary constructor
/// </summary>
/// <param name="value">The string value that this is a wrapper for</param>
private StringEnum(string value)
{
this.value = value;
}
/// <summary>
/// Compares the StringEnum to another StringEnum, or to a string value.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
if (obj is StringEnum)
{
return this.Equals((StringEnum)obj);
}
string otherString = obj as string;
if (otherString != null)
{
return this.Equals(otherString);
}
throw new ArgumentException("obj is neither a StringEnum nor a String");
}
/// <summary>
/// Strongly typed equals method.
/// </summary>
/// <param name="other">Another StringEnum to compare this object to.</param>
/// <returns>True if the objects are equal.</returns>
public bool Equals(StringEnum other)
{
return value == other.value;
}
/// <summary>
/// Equals method typed to a string.
/// </summary>
/// <param name="other">A string to compare this object to.
/// There must be a Group associated with that string.</param>
/// <returns>True if 'other' represents the same Group as 'this'.</returns>
public bool Equals(string other)
{
return value == other;
}
/// <summary>
/// Overridden equals operator, for convenience.
/// </summary>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns>True if the objects are equal.</returns>
public static bool operator ==(StringEnum first, StringEnum second)
{
return object.Equals(first, second);
}
public static bool operator !=(StringEnum first, StringEnum second)
{
return !object.Equals(first, second);
}
/// <summary>
/// Properly overrides GetHashCode so that it returns the hash of the wrapped string.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return value.GetHashCode();
}
/// <summary>
/// returns the internal string that this is a wrapper for.
/// </summary>
/// <param name="stringEnum"></param>
/// <returns></returns>
public static implicit operator string(StringEnum stringEnum)
{
return stringEnum.value;
}
/// <summary>
/// Parses a string and returns an instance that corresponds to it.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static StringEnum Parse(string input)
{
return AllValues.Where(item => item.value == input).FirstOrDefault();
}
/// <summary>
/// Syntatic sugar for the Parse method.
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public static explicit operator StringEnum(string other)
{
return Parse(other);
}
/// <summary>
/// A string representation of this object.
/// </summary>
/// <returns></returns>
public override string ToString()
{
return value;
}
}
I try to use the except method with a custom equality comparer, but it is not work.
My equality comparer:
public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject
{
#region IEqualityComparer<T> Members
/// <summary>
/// Determines whether the specified objects are equal.
/// </summary>
/// <param name="x">The first object of type <paramref name="T"/> to compare.</param>
/// <param name="y">The second object of type <paramref name="T"/> to compare.</param>
/// <returns>
/// <see langword="true"/> If the specified objects are equal; otherwise, <see langword="false"/>.
/// </returns>
public bool Equals(T x, T y)
{
return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <param name="obj">The object to get the hash code.</param>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
/// <exception cref="T:System.ArgumentNullException">
/// The type of <paramref name="obj"/> is a reference type and <paramref name="obj"/> is null.
/// </exception>
public int GetHashCode(T obj)
{
if (obj == null)
{
throw new ArgumentNullException("obj");
}
return obj.GetHashCode();
}
#endregion
}
My except usage:
BusinessObjectGuidEqualityComparer<Area> comparer = new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = this.Areas.Except(allocatedAreas, comparer);
IEnumerable<Area> toAdd = allocatedAreas.Except(this.Areas, comparer);
The strange thing is, event I provide my custom equality comparer the default one is used, so what do I make wrong?
Thanks for help.
Similar to Marc I just tested this, everything is being called just fine, my guess is that you are caught by the LINQ deferred execution, notice the ToArray in my code.
Note, when tracing through this I noticed GetHashCode is never called on null objects in the comparer.
Keep in mind, MiscUtil has an awesome way for you to do this stuff inline, see: Can I specify my explicit type comparator inline?
Or you could adapt this to Except: Distinct list of objects based on an arbitrary key in LINQ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1 {
public class BusinessObject {
public Guid Guid { get; set; }
}
public class BusinessObjectGuidEqualityComparer<T> : IEqualityComparer<T> where T : BusinessObject {
#region IEqualityComparer<T> Members
public bool Equals(T x, T y) {
return (x == null && y == null) || (x != null && y != null && x.Guid.Equals(y.Guid));
}
/// </exception>
public int GetHashCode(T obj) {
if (obj == null) {
throw new ArgumentNullException("obj");
}
return obj.GetHashCode();
}
#endregion
}
class Program {
static void Main(string[] args) {
var comparer = new BusinessObjectGuidEqualityComparer<BusinessObject>();
List<BusinessObject> list1 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()}
};
List<BusinessObject> list2 = new List<BusinessObject>() {
new BusinessObject() {Guid = Guid.NewGuid()},
new BusinessObject() {Guid = Guid.NewGuid()},
null,
null,
list1[0]
};
var toRemove = list1.Except(list2, comparer).ToArray();
var toAdd = list2.Except(list1, comparer).ToArray();
// toRemove.Length == 1
// toAdd.Length == 2
Console.ReadKey();
}
}
}
Try:
public int GetHashCode(T obj) {
return obj == null ? 0 : obj.Guid.GetHashCode();
}
Your hash-code must match equality (or at least, not contradict it); and your equality says "nulls are equal, otherwise compare the guid". Internally, I expect Except uses a HashSet<T>, which explains why getting GetHashCode right is so important.
Here's my test rig (using the above GetHashCode) which works fine:
public abstract class BusinessObject {
public Guid Guid { get; set; }
}
class Area : BusinessObject {
public string Name { get; set; }
static void Main() {
Guid guid = Guid.NewGuid();
List<Area> areas = new List<Area> {
new Area { Name = "a", Guid = Guid.NewGuid() },
new Area { Name = "b", Guid = guid },
new Area { Name = "c", Guid = Guid.NewGuid() },
};
List<Area> allocatedAreas = new List<Area> {
new Area { Name = "b", Guid = guid},
new Area { Name = "d", Guid = Guid.NewGuid()},
};
BusinessObjectGuidEqualityComparer<Area> comparer =
new BusinessObjectGuidEqualityComparer<Area>();
IEnumerable<Area> toRemove = areas.Except(allocatedAreas, comparer);
foreach (var row in toRemove) {
Console.WriteLine(row.Name); // shows a & c, since b is allocated
}
}
}
If your version doesn't work, you're going to have to post something about how you're using it, as it works fine for me (above).
The methods in your equality comparer doesn't match up. You are comparing the GUID of the objects, but the GetHashCode method uses the default implementation which is based on the reference, not the GUID. As different instances will get different hash codes eventhough they have the same GUID, the Equals method will never be used.
Get the hash code for the GUID in the GetHashCode method, as that is what you are comparing:
public int GetHashCode(T obj) {
if (obj == null) {
throw new ArgumentNullException("obj");
}
return obj.Guid.GetHashCode();
}
I have my enumHelper class that contains these:
public static IList<T> GetValues()
{
IList<T> list = new List<T>();
foreach (object value in Enum.GetValues(typeof(T)))
{
list.Add((T)value);
}
return list;
}
and
public static string Description(Enum value)
{
Attribute DescAttribute = LMIGHelper.GetAttribute(value, typeof(DescriptionAttribute));
if (DescAttribute == null)
return value.ToString();
else
return ((DescriptionAttribute)DescAttribute).Description;
}
my enum is something like:
public enum OutputType
{
File,
[Description("Data Table")]
DataTable
}
So far so good. All the previous work fine.
Now I want to add a new helper to return BindingList>, so I can link any enum to any combo using
BindingList<KeyValuePair<OutputType, string>> list = Enum<OutputType>.GetBindableList();
cbo.datasource=list;
cbo.DisplayMember="Value";
cbo.ValueMember="Key";
For that I added:
public static BindingList<KeyValuePair<T, string>> GetBindingList()
{
BindingList<KeyValuePair<T, string>> list = new BindingList<KeyValuePair<T, string>>();
foreach (T value in Enum<T>.GetValues())
{
string Desc = Enum<T>.Description(value);
list.Add(new KeyValuePair<T, string>(value, Desc));
}
return list;
}
But "Enum.Description(value)" is not even compiling:
Argument '1': cannot convert from 'T' to 'System.Enum'
How can I do that? Is that even possible?
Thank you.
Take a look at this article. You can do this using the System.ComponentModel.DescriptionAttribute or creating your own attribute:
/// <summary>
/// Provides a description for an enumerated type.
/// </summary>
[AttributeUsage(AttributeTargets.Enum | AttributeTargets.Field,
AllowMultiple = false)]
public sealed class EnumDescriptionAttribute : Attribute
{
private string description;
/// <summary>
/// Gets the description stored in this attribute.
/// </summary>
/// <value>The description stored in the attribute.</value>
public string Description
{
get
{
return this.description;
}
}
/// <summary>
/// Initializes a new instance of the
/// <see cref="EnumDescriptionAttribute"/> class.
/// </summary>
/// <param name="description">The description to store in this attribute.
/// </param>
public EnumDescriptionAttribute(string description)
: base()
{
this.description = description;
}
}
You then need to decorate the enum values with this new attribute:
public enum SimpleEnum
{
[EnumDescription("Today")]
Today,
[EnumDescription("Last 7 days")]
Last7,
[EnumDescription("Last 14 days")]
Last14,
[EnumDescription("Last 30 days")]
Last30,
[EnumDescription("All")]
All
}
All of the "magic" takes place in the following extension methods:
/// <summary>
/// Provides a static utility object of methods and properties to interact
/// with enumerated types.
/// </summary>
public static class EnumHelper
{
/// <summary>
/// Gets the <see cref="DescriptionAttribute" /> of an <see cref="Enum" />
/// type value.
/// </summary>
/// <param name="value">The <see cref="Enum" /> type value.</param>
/// <returns>A string containing the text of the
/// <see cref="DescriptionAttribute"/>.</returns>
public static string GetDescription(this Enum value)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
string description = value.ToString();
FieldInfo fieldInfo = value.GetType().GetField(description);
EnumDescriptionAttribute[] attributes =
(EnumDescriptionAttribute[])
fieldInfo.GetCustomAttributes(typeof(EnumDescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
description = attributes[0].Description;
}
return description;
}
/// <summary>
/// Converts the <see cref="Enum" /> type to an <see cref="IList" />
/// compatible object.
/// </summary>
/// <param name="type">The <see cref="Enum"/> type.</param>
/// <returns>An <see cref="IList"/> containing the enumerated
/// type value and description.</returns>
public static IList ToList(this Type type)
{
if (type == null)
{
throw new ArgumentNullException("type");
}
ArrayList list = new ArrayList();
Array enumValues = Enum.GetValues(type);
foreach (Enum value in enumValues)
{
list.Add(new KeyValuePair<Enum, string>(value, GetDescription(value)));
}
return list;
}
}
Finally, you can then simply bind the combobox:
combo.DataSource = typeof(SimpleEnum).ToList();
You should change:
public static string Description(Enum value)
{
...
}
to
public static string Description(T value)
{
...
}
so it accepts a value of the enumeration. Now here is where it gets tricky: you have a value, but attributes decorate the field which holds the value.
You actually need to reflect over the enumeration's fields and check the value of each against the value you've been given (results should be cached for performance):
foreach(var field in typeof(T).GetFields())
{
T fieldValue;
try
{
fieldValue = (T) field.GetRawConstantValue();
}
catch(InvalidOperationException)
{
// For some reason, one of the fields returned is {Int32 value__},
// which throws an InvalidOperationException if you try and retrieve
// its constant value.
//
// I am unsure how to check for this state before
// attempting GetRawConstantValue().
continue;
}
if(fieldValue == value)
{
var attribute = LMIGHelper.GetAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute;
return attribute == null ? value.ToString() : attribute.Description;
}
}
Edit addressing the follow-up question
The FillComboFromEnum method is missing the type parameter for the enum. Try this:
public static void FillComboFromEnum<T>(ComboBox Cbo, BindingList<KeyValuePair<T, string>> List) where T : struct
Notice I constrained the type to be a struct. It's not a full enumeration constraint, but it's closer than nothing.
Enum doesn't have a Description() method. The best you could do is have your enum implement an interface that has the Description() method. If you do that, then you can have
public static BindingList<KeyValuePair<T extends _interface_, String>> getBindingList()
and then inside of that you can refer to
T foo = ...?
foo.Description(...);