I have the following object and I want a dictionary to conditionally determine if there is a duplicate. For example, in one dictionary I only care about two properties being unique for my key. In a second dictionary, I want all the properties being unique for the key.
Question 1:
What interfaces should I override to accomplish this? (e.g. GetHashCode, IEqualityComparer, equals operator)
Question 2:
What should I do if I change a property that ultimately changes the value of the key? This is probably more relevant if I do an Dictionary since .NET framwork somehow handles this for me, but I never thought about it.
Code
public class EventData : IEqualityComparer<EventData>
{
public string ComputerName { get; set; }
public Guid? CategoryName { get; set; }
public string LogName { get; set; }
public int EventID { get; set; }
public long? EventUniqueTracker { get; set; }
public DateTime LastQueryDate { get; set; }
public DateTime? DateOfRecord { get; set; }
//public int QueryCount { get; set; }
public int QueryCount = 0 ;//
public string zData { get; set; }
public EventData(string computerName, Guid? categoryName, string logName, int eventID, long? eventUniqueTracker, int queryCount)
{
ComputerName = computerName;
CategoryName = categoryName;
LogName = logName;
EventID = eventID;
EventUniqueTracker = eventUniqueTracker;
LastQueryDate = DateTime.Now;
QueryCount = queryCount;
}
public EventData()
{
}
public override int GetHashCode()
{
return GetHashCode(HashType.ZCompCatLogEventAllData);
}
public object GetString(HashType hType)
{
switch (hType)
{
case HashType.AComputerName:
return ComputerName;
break;
case HashType.BCompAndCat:
return new { A = ComputerName, B = CategoryName };
break;
case HashType.CCompCatLog:
return new { A = ComputerName, B = CategoryName, C = LogName };
break;
case HashType.DCompCatLogEvent:
return new { A = ComputerName, B = CategoryName, C = LogName, D = EventID };
break;
case HashType.ECompCatLogEventUserDefined1:
case HashType.FCompCatLogEventUserDefined2:
case HashType.ZCompCatLogEventAllData:
return new { A = ComputerName, B = CategoryName, C = LogName, D = EventID, E = EventUniqueTracker };
default:
break;
}
return new object { };
}
public int GetHashCode(HashType hType)
{
return GetString(hType).GetHashCode();
return 1;
}
public override string ToString()
{
return ComputerName + " " + CategoryName + " " + LogName + " " + EventID + " " + EventUniqueTracker;
}
public bool Equals(EventData x, EventData y)
{
return x.ComputerName == y.ComputerName &&
x.CategoryName == y.CategoryName &&
x.LogName == y.LogName &&
x.EventID == y.EventID &&
x.EventUniqueTracker == y.EventUniqueTracker;
}
public int GetHashCode(EventData obj)
{
EventData ci = (EventData)obj;
// http://stackoverflow.com/a/263416/328397
return new { A = ci.ComputerName, B = ci.CategoryName, C = ci.LogName, D = ci.EventID, E = ci.EventUniqueTracker }.GetHashCode();
}
}
It sounds like you should be implementing IEqualityComparer<EventData> - but not within EventData itself. Create two separate implementations - one for the first notion of equality, and one for the second. Then create your dictionaries as:
var first = new Dictionary<EventData, string>(new PartialDataEqualityComparer());
var second = new Dictionary<EventData, string>(new FullDataEqualityComparer());
Or perhaps you want to treat the second case as the "natural" equality for EventData, in which case you could make EventData implement IEquatable<EventData> and not specify a comparer when creating the second dictionary.
Basically, you implement IEquatable<T> to say "an instance of this type is capable of comparing itself against an instance of T" whereas you implement IEqualityComparer<T> to say "an instance of this type is capable of comparing any two instances of T".
What should I do if I change a property that ultimately changes the value of the key?
You're stuffed, basically. You won't (or at least probably won't) be able to find that key again in your dictionary. You should avoid this as carefully as you possibly can. Personally I usually find that classes which are good candidates for dictionary keys are also good candidates for immutability.
Related
I am trying to get multiple results into a list to send back to JS to populate a grid. The first query (_mappedQuery) is getting data. I then want to end up putting the values into the _udfList object. I keep getting variances on the error 'cannot convert queryable to class'
I have tried setting as lists, creating query objects, single class objects. All no luck so far
MemberMNCFormsList _udfList = new MemberMNCFormsList();
foreach (var _row in _udfTables) {
System.Diagnostics.Debugger.Break();
System.Diagnostics.Debugger.Launch();
var _mappedQuery = (from res in Query<UdfColumnMapping>().AsNoTracking()
join udf in Query<UserDefinedForms>().AsNoTracking() on res.Func_Area equals udf.Func_Area
join ds in Query<Data_Set>().AsNoTracking() on res.Data_ID equals ds.DATA_ID
join df in Query<DEFITEM>().AsNoTracking() on ds.DEF_ID equals df.DEF_ID
where udf.UserDefinedForms_ID == _row.UserDefinedForms_ID &&
(res.FieldName.ToLower().StartsWith("reviewname") ||
res.FieldName.ToLower().StartsWith("disposition") ||
res.FieldName.ToLower().StartsWith("reviewdate"))
select (new MemberMNCForms {
UserDefinedFormData_ID = _row.UserDefinedFormData_ID,
FormId = udf.UserDefinedForms_ID,
MappedColumnName = res.MappedColumnName,
FieldName = res.FieldName,
MappedTableName = res.MappedTableName,
Reviewed_Name = _row.LAST_NAME.Trim() + ", " + _row.FIRST_NAME.Trim(),
Reviewed_Date = _row.CreateDate.GetShortDateorEmpty().ToString()
}));
var _formRow = _mappedQuery.Select(t => new MemberMNCForms {
UserDefinedFormData_ID = t.UserDefinedFormData_ID,
FormId = t.FormId,
MappedColumnName = t.MappedColumnName,
FieldName = t.FieldName,
MappedTableName = t.MappedTableName,
Reviewed_Name = t.Reviewed_Name,
Reviewed_Date = t.Reviewed_Date
})));
_udfList.list.Add(_formRow);
public sealed class MemberMNCForms {
public Guid? UserDefinedFormData_ID { get; set; }
public int FormId { get; set; }
public string Reviewed_Name { get; set; }
public string MappedColumnName { get; set; }
public string FieldName { get; set; }
public string MappedTableName { get; set; }
public int? MNCDetermination_ID { get; set; }
public string Reviewed_By { get; set; }
public string Reviewed_Date { get; set; }
}
public sealed class MemberMNCFormsList : ErrorInfo
{
public List<MemberMNCForms> list = new List<MemberMNCForms>();
public int Count { get; set; }
}
I am trying to get the _udfList object populated with the values coming from _mappedQuery. The only thing I thought would work was to create a MemberMNCForms object for each record in _mappedQuery to then add to _udfList.list
_formRow is an IEnumerable<MemberMNCForms>
var _formRow = _mappedQuery.Select(t => new MemberMNCForms {
UserDefinedFormData_ID = t.UserDefinedFormData_ID,
FormId = t.FormId,
MappedColumnName = t.MappedColumnName,
FieldName = t.FieldName,
MappedTableName = t.MappedTableName,
Reviewed_Name = t.Reviewed_Name,
Reviewed_Date = t.Reviewed_Date
})));
Here you are trying to add an IEnumerable<MemberMNCForms> to a List<MemberMNCForms>
_udfList.list.Add(_formRow);
You can't do this with .Add. You have to use .AddRange
Try this:
_udfList.list.AddRange(_formRow);
When you use linq like that, even if there is a single item in the list that you are Selecting on, it is just an expression tree until it is iterated on.
I assume that you are expecting a collection of MemberMNCForms back so you would need use AddRange instead of Add
_udfList.list.AddRange(_formRow);
To make sure that it has been executed, you can use ToList
_udfList.list.AddRange(_formRow.ToList());
If you are just expecting a single result, you can use SingleOrDefault.
var result = _formRow.SingleOrDefault();
if (result != null) {
_udfList.list.Add(result);
}
Objective: process an object and if the object implements an expected type, I want to change a specific property value (this part is working fine), and I also would like to apply the same logic to all property lists (that I explicit point) that are of the same expected type.
I have the following code:
public abstract class BaseObject
{
public int Id { get; set; }
}
public class Father : BaseObject
{
public DateTime CreatedOn { get; set; }
public string Name { get; set; }
public IEnumerable<ChildA> Children1 { get; set; }
public IEnumerable<ChildB> Children2 { get; set; }
public IEnumerable<ChildA> Children3 { get; set; }
public IEnumerable<ChildB> Children4 { get; set; }
}
public class ChildA : BaseObject
{
public int Val1 { get; set; }
}
public class ChildB : BaseObject
{
public string Name { get; set; }
public int Total { get; set; }
}
I want to process an object by applying some changes on a specific property on the target object and on all property children that I explicit say:
public void Start()
{
var listA = new List<ChildA> { new ChildA { Id = 1, Val1 = 1 }, new ChildA { Id = 2, Val1 = 2 } };
var listB = new List<ChildB> { new ChildB { Id = 1, Name = "1", Total = 1 } };
var obj = new Father { Id = 1, CreatedOn = DateTime.Now, Name = "F1", ChildrenA = listA, ChildrenB = listB };
// I explicit tell to process only 2 of the 4 lists....
ProcessObj(obj, x => new object[] { x.Children1, x.Children2 });
}
I was able to write this function:
public void ProcessObj<T>(T obj, Expression<Func<T, object[]>> includes = null)
{
var objBaseObject = obj as BaseObject;
if (objBaseObject == null) return;
// Here I change the ID - add 100 just as an example....
objBaseObject.Id = objBaseObject.Id + 100;
if (includes == null) return;
var array = includes.Body as NewArrayExpression;
if (array == null) return;
var exps = ((IEnumerable<object>)array.Expressions).ToArray();
for (var i = 0; i < exps.Count(); i++)
{
var name = ((MemberExpression)exps[i]).Member.Name;
var childProperty = obj.GetType().GetProperties(
BindingFlags.Public | BindingFlags.Instance
).FirstOrDefault(prop => prop.Name == name);
if (childProperty == null) continue;
// NOT correct because I think I am getting a copy of the object
// and not pointing to the object in memory (by reference)
var childList = childProperty.GetValue(obj);
// TODO: loop on the list and apply the same logic as the father....
// change the ID field....
}
}
In this prototype I started writing reflection, but I really would like to avoid it if possible....
How can I do this???
Maybe I'm missing something, but it seems like you're complicating the problem by using expression trees. Can you just not use a regular Action and Func delegates to do this? Why do they need to be expression trees? Here's an example just using delegates:
public void ProcessObj<T>(T obj, Func<T, IEnumerable<object>> includes) {
var objBaseObject = obj as BaseObject;
if (objBaseObject == null) return;
// Create a reusable action to use on both the parent and the children
Action<BaseObject> action = x => x.Id += 100;
// Run the action against the root object
action(objBaseObject);
// Get the includes by just invoking the delegate. No need for trees.
var includes = includes(obj);
// Loop over each item in each collection. If the types then invoke the same action that we used on the root.
foreach(IEnumerable<object> include in includes)
{
foreach(object item in include)
{
var childBaseObject = item as BaseObject;
if(childBaseObject != null)
{
action(childBaseObject);
}
}
}
}
Useable just like before:
ProcessObj(obj, x => new object[] { x.Children1, x.Children2 });
No expression trees and no reflection, just regular delegate lambdas.
Hope that helps
I'm trying to get all the fields that had been modified, comparing two objects from the same type.
For example:
public class Order
{
public int OrderNumber {get;set;}
public DateTime OrderDate {get;set};
public string Something {get;set};
}
Then, I save a new Order:
Order order1 = new Order;
order1.OrderNumber = 1;
order1.OrderDate = DateTime.Now;
order1.Something = string.Empty;
Save(order1)
After that, somebody tries to change some information from this order and I'm trying to find out the best way to get all the fields that were changed and save into a Log.
This must work for any type of two objects;
Should be a method like
public something ReturnFields(TObject objectSaved, TObject objectChanged)
Can anyone help me?
You can use reflection to get the properties on an object, and build a series of expressions to compare each property. That way you can execute them, and for those that aren't equal, return their names to the caller.
It would need extending though if the property types themselves are not all value types as in your example, otherwise it would only be doing a check of reference equality.
public static class PropertyCompare<T>
{
public readonly static Func<T, T, List<string>> ChangedProps;
private class PropertyComparer<T>
{
public Func<T, T, bool> Compare { get; set; }
public string PropertyName { get; set; }
}
static PropertyCompare()
{
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
var firstObject = Expression.Parameter(typeof(T), "a");
var secondObject = Expression.Parameter(typeof(T), "b");
PropertyComparer<T>[] propertyComparers = new PropertyComparer<T>[properties.Length];
for (int i = 0; i < properties.Length; i++)
{
PropertyInfo thisProperty = properties[i];
Expression arePropertiesEqual = Expression.Equal(Expression.Property(firstObject, thisProperty), Expression.Property(secondObject, thisProperty));
Expression<Func<T, T, bool>> equalityFunc = Expression.Lambda<Func<T, T, bool>>(arePropertiesEqual, firstObject, secondObject);
PropertyComparer<T> comparer = new PropertyComparer<T>()
{
Compare = equalityFunc.Compile(),
PropertyName = properties[i].Name
};
propertyComparers[i] = comparer;
}
ChangedProps = new Func<T,T,List<string>>((a,b) =>
{
List<string> changedFields = new List<string>();
foreach (PropertyComparer<T> comparer in propertyComparers)
{
if (comparer.Compare(a, b))
continue;
changedFields.Add(comparer.PropertyName);
}
return changedFields;
});
}
}
public class Order
{
public int OrderNumber {get;set;}
public DateTime OrderDate {get;set; }
public string Something {get; set; }
}
static void Main(string[] args)
{
Order myOrder1 = new Order() { OrderDate = DateTime.Today, OrderNumber = 1, Something = "bleh" };
Order myOrder2 = new Order() { OrderDate = DateTime.Today.AddDays(1), OrderNumber = 1, Something = "bleh" };
List<string> changedFields = PropertyCompare<Order>.ChangedProps(myOrder1, myOrder2);
Console.ReadKey();
}
If you're using a log like txt you can make your function return's a string, like this bellow:
public string ReturnFields(TObject objectSaved, TObject objectChanged)
{
var sb = new StringBuilder();
if(!objectSaved.Name.Equals(objectChanged.Name)
{
sb.Append("Name was changed from " + objectSaved.Name +" to: " + objectChanged.Name)
}
if(!objectSaved.OrderDate.Equals(objectChanged.OrderDate)
{
sb.Append("The date whas changed from " + objectSaved.OrderDate+" to: " + objectChanged.OrderDate)
}
return sb.ToString();
}
It's just a simple way, you can read a little about Linq expressions to do it to.
I have generic list:
class BooksRegister <T>
{
private T[] Register;
public int Count { get; set; }
public BooksRegister()
{
Register = new T[100];
Count = 0;
}
public void Add(T value)
{
if (Count >= 100)
{
return;
}
Register[Count] = value;
Count ++;
}
}
then object class:
class Book
{
public String Author { get; set; }
public String Title { get; set; }
public int Quantity { get; set; }
public Book(String aut, String pav, int kiek)
{
this.Author = aut;
this.Title = pav;
this.Quantity = kiek;
}
public override string ToString()
{
return Author + " \"" + Title + "\" " + Quantity;
}
}
Then goes my Data class where I am reading information from file. I need to implement lazy initialization of object but when I do so I can't store my object in List.
public static void ReadBooks(BooksRegister<Book> allBooks)
{
StreamReader sr = new StreamReader("ListOfBooks.txt");
string line = "";
while ((line = sr.ReadLine()) != null)
{
string[] words = line.Split('|');
String tempAuthor = words[0];
String tempTitle = words[1];
int quant = Convert.ToInt32(words[2]);
Lazy<Book> tempas = new Lazy<Book>();
tempas.Value.Author = tempAuthor;
tempas.Value.Title = tempTitle;
tempas.Value.Quantity = quant;
allBooks.Add(tempas); // error here
}
How can I solve this problem? I have to use lazy initialization necessarily
If you must use lazy there are 2 ways:
You change you lazy initialization code with:
Lazy<Book> tempas = new Lazy<Book>(() => new Book(tempAuthor, tempTitle, quant));
allBooks.Add(tempas.Value);
What it does is defines an expression on how to initialize the book. This is a bad approach because you initialize lazy object on line one, and you initialize it on the second line, which basically makes using Lazy<Book> useless.
Another approach would be to change the method signature to
public static void ReadBooks(BooksRegister<Lazy<Book>> allBooks)
In this case your lazy initializing code would look like this:
Lazy<Book> tempas = new Lazy<Book>(() => new Book(tempAuthor, tempTitle, quant));
allBooks.Add(tempas);
One thing that is missing in this case is how to access Book in BooksRegister, as now it is write only object - you can add value, but there is no way to read it from outside the class.
I am trying to use the library called FakeO (https://github.com/rally25rs/FakeO)
It works fine except when there is a Guid property. Anyone have an idea what I maybe doing wrong ?
Exceptin I get is : Object of type 'System.Int32' cannot be converted to type 'System.Guid'.
here is the code
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Get a single instance of an object");
var gud = Guid.NewGuid();
var obj1 = FakeO.Create.Fake<SampleClass>(
s => s.UniqueId = FakeO.Data.Random<Guid>(),
s => s.Id = FakeO.Number.Next(),
s => s.PhoneNumber = FakeO.Phone.Number(),
s => s.SomeString = FakeO.String.Random(50));
Console.WriteLine(obj1.ToString() + "\n");
IEnumerable<SampleClass> obj2 = FakeO.Create.Fake<SampleClass>(10, s => s.Id = FakeO.Number.Next(),
s => s.PhoneNumber = FakeO.Phone.Number(),
s => s.SomeString = FakeO.String.Random(50));
foreach (var obj in obj2)
Console.WriteLine(obj.ToString());
Console.ReadKey();
}
}
public class SampleClass
{
public int Id { get; set; }
public string SomeString { get; set; }
public string PhoneNumber { get; set; }
public Guid UniqueId { get; set; }
public override string ToString()
{
var output = "ID={0},SomeString ={1},PhoneNumber = {2}";
return String.Format(output, Id, SomeString, PhoneNumber);
}
}
Guid is value type, and the author did not handle unsupported ValueType properly. He returns 0 for all unsupported value types in the Data.Random method, which is not quite nice for any struct type. According to this StackOverflow question, the last lines of Data.Random should be fixed to
if(t.IsValueType)
{
return Activator.CreateInstance(t);
}
return null;
This will return default value for struct type, which is empty Guid in case of Guid type I believe. If you want to support Guid type, you can add it in Data.Random method just before the final check of ValueType:
if (t == typeof(Guid))
return Guid.NewGuid();
I did not test my solution, but it should do.
It looks like you should be using:
FakeO.Distinct.Guid()