I'm attempting to use Enumerable.OrderBy to sort a List because ultimately I want to be able to sort by more than a single field. At the moment it only appears to work if I create a new variable var to hold the results view which means (I think) the types need to be re-cast.
Is there a method to sort a List by more than 1 field whilst retaining the original List variable and types? I.e. I'd rather end up with variable _orderedbins of type List<orderedbins>
Below is what I currently have but everything from var test = ... onwards seems a bit wrong.
public class orderedBins
{
public string Bin { get; set; }
public int Order { get; set; }
}
List<orderedbins> _orderedbins = new List<orderedbins>();
foreach (string item in splitbins)
{
string[] spbinSetting = item.Split(',');
bool bchecked = bool.Parse(spbinSetting[1]);
int border = int.Parse(spbinSetting[2]);
if (bchecked == true)
{
_orderedbins.Add(new orderedbins { bin = spbinSetting[0], Order = border });
}
}
var test =_orderedbins.OrderBy(x => x.Order);
foreach (var item in test)
{
string f = item.Bin;
int g = item.Order;
}
You know, you can perform multiple sub-sorts for an order by...
lst.OrderBy(x => x.Prop1).ThenBy(x => x.Prop2).ThenByDescending(x => x.Prop3)...
Just add a .ToList(); and introduce it with a variable, to have the result in a list variable.
EDIT:
Great suggestion by Willem, for more readability:
from x in lst
order by x.Prop1, x.Prop2, x.Prop3
select x
You can create a new sorted list without creating a new variable using
list = list.OrderBy(item => item.Field1).ThenBy(item => item.Field1).ToList();
It will still create an entirely new list though (it's not actually much of a problem to add a new variable; those are cheap. Creating a new list, doing this, is fine as long as the list isn't very large.
If you need to sort the list in place then you'll want to use a custom comparer with the List's sort method:
public class MyComparer : IComparer<MyClass>
{
public int Compare(MyClass x, MyClass y)
{
if(x.Field1 != y.Field1)
return x.Field1.CompareTo(y.Field1)
else
return x.Field2.CompareTo(y.Field2);
}
}
List<MyClass> list = new List<MyClass>();
//Populate list
list.Sort(new MyComparer());
As others suggested, using Linq's OrderBy(...).ToList() would be a cleaner way, but this will give you a new instance of the list.
To retain the original instance, consider to use List<T>.Sort():
_orderedbins.Sort(new Comparison<orderedBins>((obj1, obj2) =>
{
int result = obj1.Order.CompareTo(obj2.Order);
return result != 0 ? result : obj1.Bin.CompareTo(obj2.Bin);
}));
This will do the trick:
_orderedbins = _orderedbins.OrderBy(x => x.Order).ToList();
...but there's no real issue creating a new variable/reference.
I think this will do it (it's already a list of orderbins so no casting is required):
_orderbins = _orderbins.OrderBy(x => x.Order).ToList();
Related
I've reviewed other answers to similar questions but can't find exactly what I want. I have a dictionary of type Superclass that can store instances of either of 2 subclasses. I need to be able to access a member of one of the subclasses. I can't figure out how to cast in the LINQ query to the subclass to access it. here's what I have.
class VirtualFabric
{ Dictionary<string, SANSwitch} MemberSwitches = new Dictionary<string, SANSwitch>();
class SANSwitch
{
string SwitchWWN {get; set; }
Dictionary<int, VirtualFabric> VirtualFabrics = new Dictionary<int, VirtualFabric>();
}
class CiscoSwitch : SanSwitch {}
class BrocadeSwitch : SanSwitch
{
public Dictionary<int, string> VirtualWWNList = new Dictionary<int, string>();
public bool HasWWN(string wwn)
{
if (wwn.StartsWith("55")) { return this.VirtualWWNList.Values.Contains(wwn); }
else { return this.SwitchWWPN.Contains(wwn); }
}
}
So the MemberSwitches in a VirtualFabric can store instances of either CiscoSwitch or BrocadeSwitch. I'm trying to do this:
List<SwitchPort> isls = this.ISLs.SelectMany(p => p.Value.Where(i => i is FCPort)).ToList();
// set up the ISLs
// if the remote switch port list already contains a port, then that switch log has been processed
// so do nothing
foreach (SwitchPort p in isls)
{
var bs = this.VirtualFabricList.SelectMany(t => t.Value.MemberSwitches.Values.Where
(s => s.Value.HasWWN(p.RemoteSwitchWWPN)));
// do something with any results
} // foreach
The SelectMany is not compiling because HasWWN() is defined in BrocadeSwitch and not SANSwitch. I could move it to SANSwitch but don't want to. I could also get all the switches in MemberSwitches of type Brocade or iterate over and declare them as Brocade and then search that list, but I'm wondering if there's a way to cast within the LINQ statement. I have seen examples of Select of a certain type, but that's not what I want.
Here is a class which derives another class:
public class Class1 { }
public class Class2 : Class1 {
public List<Class2> Twos { get { return new List<Class2>(); } }
}
Here is how to cast:
var c1 = new Class2();
var c2 = new Class2();
var l = new List<Class2> { c1, c2 };
var ints = l.SelectMany( x => x.Twos ).Cast<Class1>();
Or just cast the enumerator instead of casting each object like this:
var ints = l.SelectMany( x => x.Twos ) as IEnumerable<Class1>;
Since Class2 derives Class1, you can do the cast. However, if you try and cast from Class1 to Class2 it will fail because Class2 is more specific.
EDIT
You have this lambda:
var bs = this.VirtualFabricList.SelectMany(t => t.Value.MemberSwitches.Values.Where
(s => s.Value.HasWWN(p.RemoteSwitchWWPN)));
MemberSwithches.Values will return SANSwitch and then you are trying to call s.Value.HasWWN method but CiscoSwitch does not have HasWWN method. You cannot do that. You need to do this:
var bs = this.VirtualFabricList.SelectMany( t => t.Value.MemberSwitches.Values
.Where( s => s.Value is BrodcadeSwitch ) ).Cast<BrodcadeSwitch>()
.Where( s => s.HasWWN( p.RemoteSwitchWWPN ) );
You forgot to describe the type VirtualFabricList.
From you linq statement I gather that a VirtualFabricList is not a sequence of VirtualFabric objects, because each "t" in your VirtualFabricList has a property Value, a property not in your VirtualFabric class.
However, from the rest of your linq I gather that each t.Value is a VirtualFabric. Furthermore each t.Value has a property MemberSwitches which is a dictionary containing SANSwitch objects.
You want only some objects from this dictionary: only those SANSwitches in the dictionary that are BrocadeSwitch objects. And not even all BrocadeSwitches, but only those where HasWwn(...) returns true.
Your problem is that you check all elements in the dictionary of SANSwitches for HasWWN, while you only can only check the BrocadeSwitch ones. For this you can use Enumerable.OfType
In small steps:
IEnumerable<VirtualFabric> virtualFabrics = this.VirtualFabricList
.Select(t => t.Value>);
IEnumerable<Dictionary<string, SANSwitch>> dictionaries = virtualFabrics
.Select(fabric => fabric.MemberSwitches);
IEnumerable<SanSwitch> sanSwitches = dictionaries
.SelectMany(dictionary => dictionary.Values);
Note: if my guesses about your VirtualFacbricList are incorrect, use your own statements to get a sequence of SanSwitch objects. From this sequence you can get the BrocadeSwitches:
IEnumerable<SanSwitch> sanSwitches = ...
IEnumerable<BrocadeSwitch> brocadeSwitches = sanSwitches.OfType<BrocadeSwitch>();
IEnumerable<BrocadeSwitch> switchesWithWWN = brocadeSwitches
.Select(brocadeSwitch => brocadeSwitch.HasWWN(...)
So the key feature is Enumerable.OfType
These small steps help you to check that each linq statement contains the items you expect. It won't slow down your process, because of deferred execution those statements will not be executed until your ToList().
Of course, if readability and debug-ability is not the highest on your priority list you can put these statements into one big linq statement.
I've looked at many similar questions on stackoverflow, but I haven't seen an exact match for my problem.
I need to compare two "lists of nested lists" and capture the differences. One is an "old" list and the other is a "new" list. When comparing the nested lists, they can be considered equal if all of the NESTED list items (the MyObject.Ids) are present in both lists in order (you can assume that the nested MyObject.Ids lists are already sorted and that there are no duplicates). The MyObject.Id and MyObject.Name properties are not considering in the equality comparison, but they are still important metadata for MyObject's which should not get lost.
I am not looking for a boolean indicator of equality. Instead I need to create three new lists which capture the differences between the old and new lists (e.g. a list of items which were Added, a list of items which were Removed, and a list of items which were present in both lists).
Below is an example of some code which does exactly what I want! What I would like to know is how to make this shorter/better/simpler (cutting out one of the for loops would be a good start). To make things trickier, please assume that you cannot make any changes to the MyObject class or use any custom Equals/IEqualityComparer etc implementations.
public class MyObject
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Guid> Ids { get; set; }
}
...
// Get the list of existing objects (assume this returns some populated list)
List<MyObject> existingObjects = GetExistingObjects();
// Create a list of updated objects
List<MyObject> updatedObjects = new List<MyObject>()
{
new MyObject()
{
Ids = new List<Guid>() { new Guid("48af3cb9-945a-4ab9-91e4-7ee5765e5304"), new Guid("54b5128a-cf53-436c-9d88-2ef7abd15140") }
},
new MyObject()
{
Ids = new List<Guid>() { new Guid("0485382f-8f92-4a71-9eba-09831392ceb9"), new Guid("3d8b98df-caee-41ce-b802-2f0c5f9742de") }
}
};
// Do the comparison and capture the differences
List<MyObject> addedObjects = new List<MyObject>();
List<MyObject> removedObjects = new List<MyObject>();
List<MyObject> sameObjects = new List<MyObject>();
foreach (MyObject obj in updatedObjects)
{
if (existingObjects.Any(list => list.Ids.SequenceEqual(obj.Ids)))
{
sameObjects.Add(obj);
continue;
}
addedObjects.Add(obj);
}
foreach (MyObject obj in existingObjects)
{
if (!updatedObjects.Any(list => list.Ids.SequenceEqual(obj.Ids)))
{
removedObjects.Add(obj);
}
}
Here is a little shorter (due to elimination of the second loop) and little better (due to elimination of the ineffective search contained in the second loop). Still O(N^2) time complexity due to ineffective search contained in the loop though.
var addedObjects = new List<MyObject>();
var removedObjects = new List<MyObject>(existingObjects);
var sameObjects = new List<MyObject>();
foreach (var newObject in updatedObjects)
{
int index = removedObjects.FindIndex(oldObject => oldObject.Ids.SequenceEqual(newObject.Ids));
if (index < 0)
addedObjects.Add(newObject);
else
{
removedObjects.RemoveAt(index);
sameObjects.Add(newObject);
}
}
Update: A shorter, but IMO definitely not better (in fact worse performance wise) version
var addedObjects = updatedObjects.Where(newObject => !existingObjects.Any(oldObject => oldObject.Ids.SequenceEqual(newObject.Ids))).ToList();
var removedObjects = existingObjects.Where(oldObject => !updatedObjects.Any(newObject => newObject.Ids.SequenceEqual(oldObject.Ids))).ToList();
var sameObjects = updatedObjects.Where(newObject => !addedObjects.Any(addedObject => addedObject.Ids.SequenceEqual(newObject.Ids))).ToList();
If MyObject does not define custom equality comparison, i.e. uses default reference equality, the last line could be replaced with shorter and better performing
var sameObjects = updatedObjects.Except(addedObjects);
You can use Intersect and Except function in Linq
With Intersect you will get existing object,
and with Except you will get new objects.
Example of Except from MSDN:
double[] numbers1 = { 2.0, 2.1, 2.2, 2.3, 2.4, 2.5 };
double[] numbers2 = { 2.2 };
IEnumerable<double> onlyInFirstSet = numbers1.Except(numbers2);
foreach (double number in onlyInFirstSet)
Console.WriteLine(number);
I have a
List<object> list = new List<object>();
while (myReader.Read())
{
string arrKablan = myReader["arrK_Title"].ToString();
string arrTotal = myReader["arrTotal"].ToString();
string _title = myReader["MF_Title"].ToString();
string _path = myReader["MF_Path"].ToString();
int _level = Convert.ToInt32(myReader["MF_Level"].ToString());
list.Add(new { title = _title, path = _path, kablanim = arrKablan, total = arrTotal, level = _level });
}
I need to select just items where level == 1
i tried
list = list.where(item => item.level == 1);
but i get an error
'object' does not contain a definition for 'level' and no extension method 'level' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
i know that the compiler can get the type so he can know what it is "level".
how can i achieve this kind of select, without to define a class ?
You have two ways of fixing this:
Use a List<dynamic> instead of a List<object>. This will disable type checks. Drawback: This will disable type checks. :-)
Let the compiler infer the correct type of your list. To do this, have your data layer return a DataTable instead of a DataReader and then use LINQ to create the list:
var myList = (from drow in myDataTable.AsEnumerable()
select new {
kablanim = drow["arrK_Title"].ToString(),
total = drow["arrTotal"].ToString(),
...
}).ToList();
I can't see why you don't just make a concrete class:
public class Foo
{
public string Title { get; set; }
public string Path { get; set; }
// etc, etc
}
Then
List<Foo> list = new List<Foo>();
while (myReader.Read())
{
string arrKablan = myReader["arrK_Title"].ToString();
string arrTotal = myReader["arrTotal"].ToString();
string _title = myReader["MF_Title"].ToString();
string _path = myReader["MF_Path"].ToString();
int _level = Convert.ToInt32(myReader["MF_Level"].ToString());
list.Add(new Foo { Title = _title, Path = _path, /* etc, etc */ });
}
then you call becomes
list = list.Where(item => item.Level == 1).ToList();
(Note the additional ToList call required to make the list assignment valid)
Just for completeness, you can also do this. Create a function to get a value from any object using reflection:
private T GetValue<T>(object obj, string property)
{
return (T)obj.GetType()
.GetProperties()
.Single(p => p.Name == property)
.GetValue(obj);
}
And call it like this:
var filteredList = list.Where(item => GetValue<int>(item, "level") == 1);
You can get value of a property on anonymous class like this:
var anon = new { Level = "level", Time = DateTime.Now };
Type type = anon.GetType();
var props = type.GetProperties();
foreach (var propertyInfo in props)
{
if (propertyInfo.Name == "Level")
{
var x =propertyInfo.GetValue(anon);
}
}
I'm not sure if it is the best way to achieve that, but it is certainly possible.
You are adding object of anonymous class to the list. You can refer to this anonymous class field only inside the method you've defined it in and you should probably avoid adding it to the list, because there is now other way other then reflection or dynamic to access field of theese objects.
For example, you can access one of the elements like this:
var list = new List();
list.Add(new { field1 = "a", field2 = 2 });
list.Add(new { field1 = "b", field2 = 3 });
list.Add(new { field1 = "c", field2 = 4 });
dynamic o = list[1];
Console.WriteLine(o.field1);
Console.WriteLine(o.field2);
But you should be aware, that dynamic feature has a big overhead on every member access.
If you really want to use lambdas, you can rewrite
list = list.where(item => item.level == 1);
like this
var filtered = list.Where(item =>
{
dynamic ditem = item;
return ditem.Level == 1;
});
but this is a bad approach.
The other approach is to use reflection and rewrite this lambda like this
var filtered = list.Where(item =>
{
var field = item.GetType().GetField("level");
return (int)field.GetValue(item) == 1;
});
This is better than using dynamic because it has a smaller overhead, but can still be very costly.
Also it would probably be better to cache FieldInfo object outside of loop if your anonymous objects have same type. It can be done like this
var field = list.First().GetType().GetField("level");
var filtered = list.Where(item => (int)field.GetValue(item) == 1);
For performance reasons, Linq depends on metadata being available at compile time. By explicitly declaring List<object> you have typed the elements of this list as object which does not have a member level.
If you want to use Linq like this you have two options.
Declare a class with a level member and use it to type the collection
Declare an interface with a level member and use it to cast in the lambda expression
Option 1 is the preferred approach. Normally Linq is used with a database and the classes are generated by Visual Studio directly from the database. This is why nobody complains about the need for classes to supply metadata.
The following line creates anonymous class.
new { title = _title, path = _path, kablanim = arrKablan, total = arrTotal, level = _level });
You can't cast then your objects to anything meaningfull.
Objects don't have those properties.
You have to create a class by your own and use it.
I have a list of ids, and the items with these ids shall be removed from a Collection.
foreach(string id in list) {
myitemcollection.Remove(id); // This does not exist. How would I implement it?
}
Unfortunately, "Remove" takes a complete item, which I don't have, and "RemoveAt" takes an index, which I don't have either.
How can I achieve this? Nested loops will work, but is there a better way?
One way would be to use linq:
foreach(string id in list) {
//get item which matches the id
var item = myitemcollection.Where(x => x.id == id);
//remove that item
myitemcollection.Remove(item);
}
If mycollection is also a list of ints, you could use
List<int> list = new List<int> {1,2,3};
List<int> myitemcollection = new List<int> {1,2,3,4,5,6};
myitemcollection.RemoveAll(list.Contains);
If it is a custom class, lets say
public class myclass
{
public int ID;
}
you could use
List<int> list = new List<int> {1,2,3};
List<myclass> myitemcollection = new List<myclass>
{
new myclass { ID = 1},
new myclass { ID = 2},
new myclass { ID = 3},
new myclass { ID = 4},
new myclass { ID = 5},
new myclass { ID = 6},
};
myitemcollection.RemoveAll(i => list.Contains(i.ID));
List.RemoveAll Method
Removes all the elements that match the conditions defined by the
specified predicate.
Try using linq:
var newCollection = myitemcollection.Where(x=> !list.Contains(x.ID));
Please note that:
This assumes that your Item collection has data member called ID.
This is not the best performance wise...
If I understood your question rightly, try the below code snip
foreach (string id in list)
{
if (id == "") // check some condition to skip all other items in list
{
myitemcollection.Remove(id); // This does not exist. How would I implement it?
}
}
If this is not good enough. Make your question more clear to get exact answer
In terms of theory, you are dealing with a matter called closure.Within a loop (or for), you should make a copy of your list (or array or what you are iterating) in every way (that is mentioned differently by guys), mark those you want to remove and then deal with them out of the loop.
Given a class:
class foo
{
public string a = "";
public int b = 0;
}
Then a generic list of them:
var list = new List<foo>(new []{new foo(), new foo()});
If I am to assign multiple properties inside the following List<T> ForEach() method, is there a simpler way to do it that below? Hopefully I'm being a bit thick.
// one property - easy peasy
list.ForEach(lambda => lambda.a="hello!");
// multiple properties - hmm
list.ForEach(lambda => new Action(delegate() { lambda.a = "hello!"; lambda.b = 99;}).Invoke());
Edit: Thought ForEach() was a LINQ extension method, when it's actually part of List<T> oops!
All you need to do is introduce some brackets so that your anonymous method can support multiple lines:
list.ForEach(i => { i.a = "hello!"; i.b = 99; });
Anonymous method is your friend
list.ForEach(item =>
{
item.a = "hello!";
item.b = 99;
});
MSDN:
Anonymous Methods (C# Programming Guide)
list.ForEach(lamba=>lambda.a="hello!");
Becomes
list.ForEach(item=>{
item.a = "hello!";
item.b = 99;
});
Of course you can also assign them when you create the list like :
var list = new List<foo>(new []{new foo(){a="hello!",b=99}, new foo(){a="hello2",b=88}});
list.ForEach(i => i.DoStuff());
public void DoStuff(this foo lambda)
{
lambda.a="hello!";
lambda.b=99;
}
Honestly, there's really no need to use List.ForEach here:
foreach (var item in list) { item.a="hello!"; item.b=99; }