I have a class which implements IList interface. I requires a "sorted view" of this list, but without modifying it (I cannot sort directly the IList class).
These view shall be updated when the original list is modified, keeping items sorted. So, I've introduced a SortList creation method which create a SortList which has a comparer for the specific object contained in the original list.
Here is the snippet of code:
public class MyList<T> : ICollection, IList<T>
{
public SortedList CreateSortView(string property)
{
try
{
Lock();
SortListView sortView;
if (mSortListViews.ContainsKey(property) == false)
{
// Create sorted view
sortView = new SortListView(property, Count);
mSortListViews.Add(property, sortView);
foreach (T item in Items)
sortView.Add(item);
} else
sortView = mSortListViews[property];
sortView.ReferenceCount++;
return (sortView);
}
finally
{
Unlock();
}
}
public void DeleteSortView(string property)
{
try
{
Lock();
// Unreference sorted view
mSortListViews[property].ReferenceCount--;
// Remove sorted view
if (mSortListViews[property].ReferenceCount == 0)
mSortListViews.Remove(property);
}
finally
{
Unlock();
}
}
protected class SortListView : SortedList
{
public SortListView(string property, int capacity)
: base(new GenericPropertyComparer(typeof(T).GetProperty(property, BindingFlags.Instance | BindingFlags.Public)), capacity)
{
}
public int ReferenceCount = 0;
public void Add(T item)
{
Add(item, item);
}
public void Remove(T item)
{
base.Remove(item);
}
class GenericPropertyComparer : IComparer
{
public GenericPropertyComparer(PropertyInfo property)
{
if (property == null)
throw new ArgumentException("property doesn't specify a valid property");
if (property.CanRead == false)
throw new ArgumentException("property specify a write-only property");
if (property.PropertyType.GetInterface("IComparable") == null)
throw new ArgumentException("property type doesn't IComparable");
mSortingProperty = property;
}
public int Compare(object x, object y)
{
IComparable propX = (IComparable)mSortingProperty.GetValue(x, null);
IComparable propY = (IComparable)mSortingProperty.GetValue(y, null);
return (propX.CompareTo(propY));
}
private PropertyInfo mSortingProperty = null;
}
private Dictionary<string, SortListView> mSortListViews = new Dictionary<string, SortListView>();
}
Practically, class users request to create a SortListView specifying the name of property which determine the sorting, and using the reflection each SortListView defined a IComparer which keep sorted the items.
Whenever an item is added or removed from the original list, every created SortListView will be updated with the same operation.
This seems good at first chance, but it creates me problems since it give me the following exception when adding items to the SortList:
System.ArgumentException: Item has already been added. Key in dictionary: 'PowerShell_ISE [C:\Windows\sysWOW64\WindowsPowerShell\v1.0\PowerShell_ISE.exe]' Key being added: 'PowerShell_ISE [C:\Windows\system32\WindowsPowerShell\v1.0\PowerShell_ISE.exe]'
As you can see from the exception message, thrown by SortedListView.Add(object), the string representation of the key (the list item object) is different (note the path of the executable).
Why SortList give me that exception?
To solve this I tried to implement a GetHashCode() for the underlying object, but without success:
public override int GetHashCode()
{
return (
base.GetHashCode() ^
mApplicationName.GetHashCode() ^
mApplicationPath.GetHashCode() ^
mCommandLine.GetHashCode() ^
mWorkingDirectory.GetHashCode()
);
}
If I understood you correctly, your purpose is just to get a view of you list, sorted by a property of the object.
Then, why use SortedList that requires unique Keys, when you could easily get your result using LINQ OrderBy (or if you're using .net 2.0 List.Sort()) ?
Hence, for example, your CreateSortView could be implemented in this way:
(omitting lock, try/finally and reference counting)
public IList<T> CreateSortView(string property)
{
IList<T> sortView;
if (mSortListViews.ContainsKey(property) == false)
{
// Create sorted view
sortView = this.OrderBy(x => x, new GenericPropertyComparer<T>(property)).ToList();
mSortListViews.Add(property, sortView);
}
else
{
sortView = mSortListViews[property];
}
return sortView;
}
With GenericPropertyComparer implemented as follows:
class GenericPropertyComparer<T> : IComparer<T>
{
public GenericPropertyComparer(string propertyName)
{
var property = typeof(T).GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public);
if (property == null)
throw new ArgumentException("property doesn't specify a valid property");
if (property.CanRead == false)
throw new ArgumentException("property specify a write-only property");
if (property.PropertyType.GetInterface("IComparable") == null)
throw new ArgumentException("property type doesn't IComparable");
mSortingProperty = property;
}
public int Compare(T x, T y)
{
IComparable propX = (IComparable)mSortingProperty.GetValue(x, null);
IComparable propY = (IComparable)mSortingProperty.GetValue(y, null);
return (propX.CompareTo(propY));
}
private PropertyInfo mSortingProperty = null;
}
EDIT:
If you need to add/remove items from your sorted collection frequeltly, maybe use a SortedList would be better, but the problem with SortedList is that it needs unique keys, and in your case you can't assure that.
Anyway, you can use a custom sorted List that doesn't need unique values, look at the link below for a simple implementation:
Implementation of sorted IList<T> that doesn't require unique values
It seems to me it is a multithreading issue. I can't see what the Lock() function is doing in your code, but I think you will have more luck by surrounding the dictionary access code with a standard lock:
lock(this){
SortListView sortView;
if (mSortListViews.ContainsKey(property) == false) {
// Create sorted view
sortView = new SortListView(property, Count);
mSortListViews.Add(property, sortView);
foreach (T item in Items)
sortView.Add(item);
} else
sortView = mSortListViews[property];
sortView.ReferenceCount++;
}
and the same in the removing part.
Thanks to digEmAll's comment, I found a quick solution: The IComparer implementation shall return 0 only on objects really equals!
So:
public int Compare(object x, object y)
{
IComparable propX = (IComparable)mSortingProperty.GetValue(x, null);
IComparable propY = (IComparable)mSortingProperty.GetValue(y, null);
int compare;
if ((compare = propX.CompareTo(propY)) == 0) {
if (x.GetHashCode() < y.GetHashCode())
return (-1);
else if (x.GetHashCode() > y.GetHashCode())
return (+1);
else return (0);
} else
return (compare);
}
Related
This question already has answers here:
How does HashSet compare elements for equality?
(5 answers)
Closed 7 years ago.
i don't know how but for weeks now i was using a HashSet<myObject> collection of mainly strings members as i really though it's internally uses a builtin approach as dictionary to avoid duplicate items in a non KVP format of data(single columns)
my scenario is :
HashSet<myHddFolderPaths> UniqColleciton = new HashSet<myHddFolderPtahs>
int countRounds=0;
void addToCollection()
{
for(int i=0, i < UniqColleciton.Count; i++)
{
add some items to UniqColleciton via Directory.GetDirectories();
}
if(countRounds++ < Limit)
addToCollection()
}
this is a pattern for dir-walker i am building and this is only an example for a scenario when recurse of same data could not be avoided so i don't recall where i have read about it and thought, that by simply using a HashSet<T> would "just take Care of business"
i haven't thought of the option it will allow duplicates but in this project i put it to a test and it did allow to my surprise to add an existing items
so my work around is :
Dictionary<string, int> fiterAccesDeniedPaths = new Dictionary<string, int>();
Dictionary<string, int> fiterAccesiblePaths = new Dictionary<string, int>();
if (this.fiterAccesDeniedPaths.ContainsKey(e.Message)) continue;
if (this.fiterAccessiblePaths.ContainsKey(object.stringPathMember)) continue;
add to filters ; UniqColleciton.Add(myHddFolderPaths);
is there a better/more efficient approach for acomplishing this task ?
public class FolderPath
{
public string DriveL { get; set; }
public string FolderLevel { get; set; }
public string Path { get; set; }
public int Fsize { get; set; }
}
public class GenericUniqCollectionM<T> : HashSet<T>
{
public GenericUniqCollectionM():base()
{
}
}
A HashSet created with the parameterless constructor uses a default equality comparer. The default comparer will use FolderPath.Equals() to check equality.
internal class ObjectEqualityComparer<T> : EqualityComparer<T>
{
public override bool Equals(T x, T y)
{
if (x != null)
{
if (y != null) return x.Equals(y);
return false;
}
if (y != null) return false;
return true;
}
public override int GetHashCode(T obj)
{
if (obj == null) return 0;
return obj.GetHashCode();
}
...
}
You didn't override Equals and GetHashCode, so it will use the default implementation provided by object, checking reference equality.
You have two options now. One is to override Equals and GetHashCode in FolderPath.
public class FolderPath
{
...
public override bool Equals(object obj)
{
if (obj == null) return false;
FolderPath other = obj as FolderPath;
if (other == null) return false;
//simple implementation, only compares Path
return Path == other.Path;
}
public override int GetHashCode()
{
if (Path == null) return 0;
return Path.GetHashCode();
}
}
The other one is to implement a custom IEqualityComparer<FolderPath>
public class FolderPathComparer : IEqualityComparer<FolderPath>
{
public bool Equals(FolderPath x, FolderPath y)
{
if (x != null)
{
if (y != null) return x.Path == y.Path;
return false;
}
if (y != null) return false;
return true;
}
public int GetHashCode(FolderPath obj)
{
if (obj == null || obj.Path == null) return 0;
return obj.Path.GetHashCode();
}
}
and pass it to the HashSet constructor.
var set = new HashSet<FolderPath>(new FolderPathComparer());
Ypu wanted your HashSet to "take care of business". HashSet does exactly that. But you first have to let it know what you consider a "duplicate" (or rather when your objects should be considered equal). To do so, you should implement (override) GetHashCode() method on your myHddFolderPaths class.
How does HashSet compare elements for equality?
Implementing GetHashCode correctly
Default implementation for Object.GetHashCode()
I'm trying to sort a List<T>, without using OrderBy, OrderByDescending, where T is a custom class.
Code:
class Something
{
public string Category { get; set; }
public int Fingers { get; set; }
public DateTime Creation { get; set; }
}
The list order it's based on any property of T.
class BigRoom
{
var Room = new Room(new List<Something>());
}
class Room<T> where T: class, new()
{
List<T> baseList;
public Room(List<T> listPar)
{
baseList = listPar;
var prop = /* get any property from T with reflection... */
// How to set a comparer here, if we know prop (type, value...)
baseList.Sort(...);
// go do something with reordered list
}
}
I can do it knowing T and its properties, using lambda expressions or delegates.
list.Sort((x, y) => x.CompareTo(y));
But when getting prop values, it returns an object, which it doesn't implement CompareTo(), is there any way of achieving this, if so I'll be grateful.
Your Room constructor can be implemented like this(note i add a random for example purposes you can have the property chosen how you like it):
using System.ComponentModel;
public Room(List<T> listPar)
{
Random r = new Random(Environment.TickCount);
baseList = listPar;
var props = TypeDescriptor.GetProperties(typeof(T));
PropertyDescriptor prop = props[r.Next(props.Count)];
// How to set a comparer here, if we know prop (type, value...)
baseList.Sort((x, y) => prop.GetValue(x).ToString().CompareTo(prop.GetValue(y).ToString()));
// go do something with reordered list
}
So if the propertydescriptor is pointing to the Fingers property for example it will sort by those values,using the compareTo of the string class.
This should get you started. You'll need to actually clean up the Compare method to handle if the values are null i.e. either weren't set or are not IComparable which is required to actually be able to do comparisons.
PropertyInfo myPropertyFromReflection = GetMyPropertySomehow();
myList.Sort(new MyComparer<TransactionRequest>(myPropertyFromReflection));
public class MyComparer<T> : IComparer<T>
{
PropertyInfo _sortBy;
public MyComparer(PropertyInfo sortBy)
{
_sortBy = sortBy;
}
public int Compare(T x, T y)
{
var xValue = _sortBy.GetValue(x) as IComparable;
var yValue = _sortBy.GetValue(y) as IComparable;
return xValue.CompareTo(yValue);
}
}
objectlist is a list of objects you want to sort based on Status, then by Customer Name, then by Company Name, then by Billing Address
Assume entries in the object list have the following properties:
Status,
Customer Name,
Company Name,
Billing Address
objectlist.Sort(delegate(Object a, Object b)
{
if (String.CompareOrdinal(a.Status, b.Status) == 0)
{
return String.CompareOrdinal(a.CustomerName, b.CustomerName) == 0 ? String.CompareOrdinal(a.CompanyName, b.CompanyName) : String.CompareOrdinal(a.BillingAddress, b.BillingAddress);
}
if (a.Status.Equals("Very Important!")) { return -1; }
if (b.Status.Equals("Very Important!")) { return 1; }
if (a.Status.Equals("Important")) { return -1; }
if (b.Status.Equals("Important")) { return 1; }
if (a.Status.Equals("Not Important")){ return -1; }
return 1;
});
Hope this helps. =)
I have some bells in my database with the same number. I want to get all of them without duplication. I created a compare class to do this work, but the execution of the function causes a big delay from the function without distinct, from 0.6 sec to 3.2 sec!
Am I doing it right or do I have to use another method?
reg.AddRange(
(from a in this.dataContext.reglements
join b in this.dataContext.Clients on a.Id_client equals b.Id
where a.date_v <= datefin && a.date_v >= datedeb
where a.Id_client == b.Id
orderby a.date_v descending
select new Class_reglement
{
nom = b.Nom,
code = b.code,
Numf = a.Numf,
})
.AsEnumerable()
.Distinct(new Compare())
.ToList());
class Compare : IEqualityComparer<Class_reglement>
{
public bool Equals(Class_reglement x, Class_reglement y)
{
if (x.Numf == y.Numf)
{
return true;
}
else { return false; }
}
public int GetHashCode(Class_reglement codeh)
{
return 0;
}
}
Your GetHashCode implementation always returns the same value. Distinct relies on a good hash function to work efficiently because it internally builds a hash table.
When implementing interfaces of classes it is important to read the documentation, to know which contract you’re supposed to implement.1
In your code, the solution is to forward GetHashCode to Class_reglement.Numf.GetHashCode and implement it appropriately there.
Apart from that, your Equals method is full of unnecessary code. It could be rewritten as follows (same semantics, ¼ of the code, more readable):
public bool Equals(Class_reglement x, Class_reglement y)
{
return x.Numf == y.Numf;
}
Lastly, the ToList call is unnecessary and time-consuming: AddRange accepts any IEnumerable so conversion to a List isn’t required. AsEnumerable is also redundant here since processing the result in AddRange will cause this anyway.
1 Writing code without knowing what it actually does is called cargo cult programming. It’s a surprisingly widespread practice. It fundamentally doesn’t work.
Try This code:
public class GenericCompare<T> : IEqualityComparer<T> where T : class
{
private Func<T, object> _expr { get; set; }
public GenericCompare(Func<T, object> expr)
{
this._expr = expr;
}
public bool Equals(T x, T y)
{
var first = _expr.Invoke(x);
var sec = _expr.Invoke(y);
if (first != null && first.Equals(sec))
return true;
else
return false;
}
public int GetHashCode(T obj)
{
return obj.GetHashCode();
}
}
Example of its use would be
collection = collection
.Except(ExistedDataEles, new GenericCompare<DataEle>(x=>x.Id))
.ToList();
If you want a generic solution that creates an IEqualityComparer for your class based on a property (which acts as a key) of that class have a look at this:
public class KeyBasedEqualityComparer<T, TKey> : IEqualityComparer<T>
{
private readonly Func<T, TKey> _keyGetter;
public KeyBasedEqualityComparer(Func<T, TKey> keyGetter)
{
if (default(T) == null)
{
_keyGetter = (x) => x == null ? default : keyGetter(x);
}
else
{
_keyGetter = keyGetter;
}
}
public bool Equals(T x, T y)
{
return EqualityComparer<TKey>.Default.Equals(_keyGetter(x), _keyGetter(y));
}
public int GetHashCode(T obj)
{
TKey key = _keyGetter(obj);
return key == null ? 0 : key.GetHashCode();
}
}
public static class KeyBasedEqualityComparer<T>
{
public static KeyBasedEqualityComparer<T, TKey> Create<TKey>(Func<T, TKey> keyGetter)
{
return new KeyBasedEqualityComparer<T, TKey>(keyGetter);
}
}
For better performance with structs there isn't any boxing.
Usage is like this:
IEqualityComparer<Class_reglement> equalityComparer =
KeyBasedEqualityComparer<Class_reglement>.Create(x => x.Numf);
Just code, with implementation of GetHashCode and NULL validation:
public class Class_reglementComparer : IEqualityComparer<Class_reglement>
{
public bool Equals(Class_reglement x, Class_reglement y)
{
if (x is null || y is null))
return false;
return x.Numf == y.Numf;
}
public int GetHashCode(Class_reglement product)
{
//Check whether the object is null
if (product is null) return 0;
//Get hash code for the Numf field if it is not null.
int hashNumf = product.hashNumf == null ? 0 : product.hashNumf.GetHashCode();
return hashNumf;
}
}
Example:
list of Class_reglement distinct by Numf
List<Class_reglement> items = items.Distinct(new Class_reglementComparer());
The purpose of this answer is to improve on previous answers by:
making the lambda expression optional in the constructor so that full object equality can be checked by default, not just on one of the properties.
operating on different types of classes, even complex types including sub-objects or nested lists. And not only on simple classes comprising only primitive type properties.
Not taking into account possible list container differences.
Here, you'll find a first simple code sample that works only on simple types (the ones composed only by primitif properties), and a second one that is complete (for a wider range of classes and complex types).
Here is my 2 pennies try:
public class GenericEqualityComparer<T> : IEqualityComparer<T> where T : class
{
private Func<T, object> _expr { get; set; }
public GenericEqualityComparer() => _expr = null;
public GenericEqualityComparer(Func<T, object> expr) => _expr = expr;
public bool Equals(T x, T y)
{
var first = _expr?.Invoke(x) ?? x;
var sec = _expr?.Invoke(y) ?? y;
if (first == null && sec == null)
return true;
if (first != null && first.Equals(sec))
return true;
var typeProperties = typeof(T).GetProperties();
foreach (var prop in typeProperties)
{
var firstPropVal = prop.GetValue(first, null);
var secPropVal = prop.GetValue(sec, null);
if (firstPropVal != null && !firstPropVal.Equals(secPropVal))
return false;
}
return true;
}
public int GetHashCode(T obj) =>
_expr?.Invoke(obj).GetHashCode() ?? obj.GetHashCode();
}
I know we can still optimize it (and maybe use a recursive?)..
But that is working like a charm without this much complexity and on a wide range of classes. ;)
Edit: After a day, here is my $10 attempt:
First, in a separate static extension class, you'll need:
public static class CollectionExtensions
{
public static bool HasSameLengthThan<T>(this IEnumerable<T> list, IEnumerable<T> expected)
{
if (list.IsNullOrEmptyCollection() && expected.IsNullOrEmptyCollection())
return true;
if ((list.IsNullOrEmptyCollection() && !expected.IsNullOrEmptyCollection()) || (!list.IsNullOrEmptyCollection() && expected.IsNullOrEmptyCollection()))
return false;
return list.Count() == expected.Count();
}
/// <summary>
/// Used to find out if a collection is empty or if it contains no elements.
/// </summary>
/// <typeparam name="T">Type of the collection's items.</typeparam>
/// <param name="list">Collection of items to test.</param>
/// <returns><c>true</c> if the collection is <c>null</c> or empty (without items), <c>false</c> otherwise.</returns>
public static bool IsNullOrEmptyCollection<T>(this IEnumerable<T> list) => list == null || !list.Any();
}
Then, here is the updated class that works on a wider range of classes:
public class GenericComparer<T> : IEqualityComparer<T> where T : class
{
private Func<T, object> _expr { get; set; }
public GenericComparer() => _expr = null;
public GenericComparer(Func<T, object> expr) => _expr = expr;
public bool Equals(T x, T y)
{
var first = _expr?.Invoke(x) ?? x;
var sec = _expr?.Invoke(y) ?? y;
if (ObjEquals(first, sec))
return true;
var typeProperties = typeof(T).GetProperties();
foreach (var prop in typeProperties)
{
var firstPropVal = prop.GetValue(first, null);
var secPropVal = prop.GetValue(sec, null);
if (!ObjEquals(firstPropVal, secPropVal))
{
var propType = prop.PropertyType;
if (IsEnumerableType(propType) && firstPropVal is IEnumerable && !ArrayEquals(firstPropVal, secPropVal))
return false;
if (propType.IsClass)
{
if (!DeepEqualsFromObj(firstPropVal, secPropVal, propType))
return false;
if (!DeepObjEquals(firstPropVal, secPropVal))
return false;
}
}
}
return true;
}
public int GetHashCode(T obj) =>
_expr?.Invoke(obj).GetHashCode() ?? obj.GetHashCode();
#region Private Helpers
private bool DeepObjEquals(object x, object y) =>
new GenericComparer<object>().Equals(x, y);
private bool DeepEquals<U>(U x, U y) where U : class =>
new GenericComparer<U>().Equals(x, y);
private bool DeepEqualsFromObj(object x, object y, Type type)
{
dynamic a = Convert.ChangeType(x, type);
dynamic b = Convert.ChangeType(y, type);
return DeepEquals(a, b);
}
private bool IsEnumerableType(Type type) =>
type.GetInterface(nameof(IEnumerable)) != null;
private bool ObjEquals(object x, object y)
{
if (x == null && y == null) return true;
return x != null && x.Equals(y);
}
private bool ArrayEquals(object x, object y)
{
var firstList = new List<object>((IEnumerable<object>)x);
var secList = new List<object>((IEnumerable<object>)y);
if (!firstList.HasSameLengthThan(secList))
return false;
var elementType = firstList?.FirstOrDefault()?.GetType();
int cpt = 0;
foreach (var e in firstList)
{
if (!DeepEqualsFromObj(e, secList[cpt++], elementType))
return false;
}
return true;
}
#endregion Private Helpers
We can still optimize it but it worth give it a try ^^.
The inclusion of your comparison class (or more specifically the AsEnumerable call you needed to use to get it to work) meant that the sorting logic went from being based on the database server to being on the database client (your application). This meant that your client now needs to retrieve and then process a larger number of records, which will always be less efficient that performing the lookup on the database where the approprate indexes can be used.
You should try to develop a where clause that satisfies your requirements instead, see Using an IEqualityComparer with a LINQ to Entities Except clause for more details.
IEquatable<T> can be a much easier way to do this with modern frameworks.
You get a nice simple bool Equals(T other) function and there's no messing around with casting or creating a separate class.
public class Person : IEquatable<Person>
{
public Person(string name, string hometown)
{
this.Name = name;
this.Hometown = hometown;
}
public string Name { get; set; }
public string Hometown { get; set; }
// can't get much simpler than this!
public bool Equals(Person other)
{
return this.Name == other.Name && this.Hometown == other.Hometown;
}
public override int GetHashCode()
{
return Name.GetHashCode(); // see other links for hashcode guidance
}
}
Note you DO have to implement GetHashCode if using this in a dictionary or with something like Distinct.
PS. I don't think any custom Equals methods work with entity framework directly on the database side (I think you know this because you do AsEnumerable) but this is a much simpler method to do a simple Equals for the general case.
If things don't seem to be working (such as duplicate key errors when doing ToDictionary) put a breakpoint inside Equals to make sure it's being hit and make sure you have GetHashCode defined (with override keyword).
In my project, my data layer keeps a number of List collections to store the last returned data from SQl DB searches. I find myself repeating a lot of code. One in particular is used to see if a data object is already in the database, so that it can be updated instead of added. Here is an example:
public List<ClassA> ListClassA;
public List<ClassB> ListClassB;
public override bool ContainsClassA(ClassA group)
{
if (null == group)
{
throw new ArgumentNullException();
}
return ListClassA.Where(x => x.ClassA_ID == group.ClassA_ID).ToList().Count > 0;
}
public override bool ContainsClassB(ClassB group)
{
if (null == group)
{
throw new ArgumentNullException();
}
return ListClassB.Where(x => x.ClassB_ID == group.ClassB_ID).ToList().Count > 0;
}
Is there a way in which I can do this using the one function and Generics?
Would I need to rename the index fields so that they match e.g. ClassA_ID and ClassB_ID to ID?
I would use a Dictionary instead of a List for caching:
Dictionary<ClassA_ID, ClassA> classACache;
...
classACache.ContainsKey(aitem.ClassA_ID);
Have both classes implement an interface with an ID property, and then use a generic with a constraint (where T : [your interface name]).
Also,
ListClassA.Where(x => x.ClassA_ID == group.ClassA_ID).ToList().Count > 0
is a bit redundant, you could just use
ListClassA.Any(x => x.ClassA_ID == group.ClassA_ID)
You can implement IEquatable<T> on your classes like this:
public class ClassA : IEquatable<ClassA>
{
public int ID { get; set; }
public bool Equals(ClassA other)
{
if (this.ID == other.ID)
return true;
return false;
}
}
Then you can simply use the List<T>.Contains() method like this:
List<ClassA> ListOfClassA = new List<ClassA>();
ClassA ItemA = new ClassA() { ID = 1 };
ClassA ItemB = new ClassA() { ID = 2 };
ListOfClassA.Add(ItemA);
if (ListOfClassA.Contains(ItemA))
Console.WriteLine("The list contains ItemA.");
if (ListOfClassA.Contains(ItemB))
Console.WriteLine("The list contains ItemB.");
The output of the above code is:
The list contains ItemA.
Does .Net provides generic form of NameValueCollection or an alternative to
Dictionary<string,List<T>> ?
Something like
Person john = new Person();
...
Person vick = new Person();
...
NameValueCollection<Person> stringToPerson = new NameValueCollection<Person>();
stringToPerson.Add("John",john)
stringToPerson.Add("Vick",vick)
Actually in my case am forced to rely on Dictionary<string,List<Peron>>, is there any other alternative?
Regards,
Jeez
There's no such thing built in to the BCL as far as I know. I would just write your own class which wraps a Dictionary<string, List<T>> internally and exposes appropriate methods (e.g., Add could add an element to the List<T> for the given key).
For example:
class NameValueCollection<T>
{
Dictionary<string, List<T>> _dict = new Dictionary<string, List<T>>();
public void Add(string name, T value)
{
List<T> list;
if (!_dict.TryGetValue(name, out list))
{
_dict[name] = list = new List<T>();
}
list.Add(value);
}
// etc.
}
The closest alternative is probably the ILookup<TKey, TElement> interface. At the moment, the only public type that implements it in the BCL is the immutable Lookup<TKey, TElement> class, an instance of which can be created with the Enumerable.ToLookup method. If you want a mutable type that implements the interface, you'll have to write one yourself; you can find an example implementation here.
In your case, you probably want an ILookup<string, Person>.
Oh, I see what you want to do now. You want to be able to add to the Person collection without having to create a new List each time. Extension methods to the rescue!
public static void SafeAdd<TValue>(this IDictionary<TKey, ICollection<TValue>> dict, TKey key, TValue value)
{
HashSet<T> container;
if (!dict.TryGetValue(key, out container))
{
dict[key] = new HashSet<TValue>();
}
dict[key].Add(value);
}
Usage:
var names = new Dictionary<string, ICollection<Person>>();
names.SafeAdd("John", new Person("John"));
Nothing inbuilt; there is Lookup<TKey,TValue> which operates as a multi-map, but that is immutable. I wrote a mutable EditableLookup<TKey,TValue> for MiscUtil which may help.
Generic NameValueCollection
What makes NameValueCollection special unlike Dictionary, is that one key can contain several elements.
The generic NameValueCollection<T> based on NameObjectCollectionBase is in the following code:
using System.Collections.Specialized;
namespace System.Collections.Generic
{
public class NameValueCollection<T> : NameObjectCollectionBase
{
private string[] _keys; // Cached keys.
private T[] _values; // Cached values.
// Resets the caches.
protected void InvalidateCachedArrays()
{
_values = null;
_keys = null;
}
// Converts ArrayLit to Array of T elements.
protected static T[] AsArray(ArrayList list)
{
int count = 0;
if (list == null || (count = list.Count) == 0)
return (T[])null;
T[] array = new T[count];
list.CopyTo(0, array, 0, count);
return array;
}
// Gets all values cache.
protected ArrayList GetAllValues()
{
int count = Count;
ArrayList arrayList = new ArrayList(count);
for (int i = 0; i < count; ++i)
{
arrayList.AddRange(Get(i));
}
return arrayList;
}
// Adds single value to collection.
public void Add(string name, T value)
{
InvalidateCachedArrays();
ArrayList arrayList = (ArrayList)BaseGet(name);
if (arrayList == null)
{
arrayList = new ArrayList(1);
if (value != null) arrayList.Add(value);
BaseAdd(name, arrayList);
}
else
{
if (value == null) return;
arrayList.Add(value);
}
}
// Adds range of values to collection.
public void Add(NameValueCollection<T> collection)
{
InvalidateCachedArrays();
int count = collection.Count;
for (int i = 0; i < count; i++)
{
string key = collection.GetKey(i);
T[] values = collection.Get(i);
foreach (var value in values)
{
Add(key, value);
}
}
}
// Set single value (prevoious values will be removed).
public void Set(string name, T value)
{
InvalidateCachedArrays();
BaseSet(name, new ArrayList(1) { value });
}
// Set range of values (prevoious values will be removed).
public void Set(string name, params T[] values)
{
InvalidateCachedArrays();
BaseSet(name, new ArrayList(values));
}
// Gets all values that paired with specified key.
public T[] Get(string name)
{
return AsArray((ArrayList)BaseGet(name));
}
// Gets all values at the specified index of collection.
public T[] Get(int index)
{
return AsArray((ArrayList)BaseGet(index));
}
// Gets string containing the key at the specified index.
public string GetKey(int index)
{
return BaseGetKey(index);
}
// Removes values from the specified key.
public void Remove(string name)
{
InvalidateCachedArrays();
BaseRemove(name);
}
// Removes all data from the collection.
public void Clear()
{
InvalidateCachedArrays();
BaseClear();
}
// All keys that the current collection contains.
public new string[] Keys
{
get
{
if (_keys == null)
_keys = BaseGetAllKeys();
return _keys;
}
}
// All values that the current collection contains.
public T[] Values
{
get
{
if (_values == null)
_values = AsArray(GetAllValues());
return _values;
}
}
// Values at the specefied index.
public T[] this[int index]
{
get
{
return Get(index);
}
set
{
BaseSet(index, new ArrayList(value));
}
}
// Values at the specefied key.
public T[] this[string name]
{
get
{
return Get(name);
}
set
{
BaseSet(name, new ArrayList(value));
}
}
// Enumerates all entries.
public IEnumerable<KeyValuePair<string, T>> GetAllEntries()
{
foreach (string key in Keys)
{
foreach (T value in Get(key))
{
yield return new KeyValuePair<string, T>(key, value);
}
}
}
}
}
Usage:
NameValueCollection<int> collection = new NameValueCollection<int>();
collection.Add("a", 123);
collection.Add("a", 456); // 123 and 456 will be inserted into the same key.
collection.Add("b", 789); // 789 will be inserted into another key.
int[] a = collection.Get("a"); // contains 123 and 456.
int[] b = collection.Get("b"); // contains 789.
The above code implements the main features.
Here is complete implementation of the NameValueCollection<T> with additional tools.