I have 2 objects:
public class ClassA
{
public int Id
public string name;
public ClassB myObjectB;
}
public class ClassB
{
public int Id
public string name
}
Having 2 Lists for <ClassA> <ClassB>
Some items from List1 match by Id with an item on List2... I want to set the objectB foreach item...
foreach(ClassA item in List1)
{
ClassB obj = (from b in List2 where b.Id == item.Id select b).SingleOrDefault()
if(obj != null)
{
item.myObjectB = obj;
////////break; <- ignore this
}
}
This solution works for me, but I'm just wondering if there is a better way to do this, instead of Foreach
Thanks everyone for your help!!!
I think that a foreach, in this case, is actually the appropriate approach, as you're mutating your list. You could potentially simplify your code a bit, however:
foreach(ClassA item in List1)
{
item.myObjectB = List2.FirstOrDefault(b => b.Id == item.Id);
}
This will set the item every time, though it will be set to null if there is no match. If you already have items in myObjectB and setting them to null is inappropriate, you could use:
foreach(ClassA item in List1)
{
item.myObjectB = List2.FirstOrDefault(b => b.Id == item.Id) ?? item.myObjectB;
}
Expanding on Reed's answer.. You can actually do this in a one-liner, because a list has a ForEach method.
List1.ForEach(item => item.myObjectB = List2.FirstOrDefault(b => b.Id == item.Id) ?? item.myObjectB);
List<ClassA> list1 = new List<ClassA>();
List<ClassB> list2 = new List<ClassB>();
list1.Add(new ClassA { Id = 2, name = "a2" });
list1.Add(new ClassA { Id = 3, name = "a3" });
list1.Add(new ClassA { Id = 4, name = "a4" });
list1.Add(new ClassA { Id = 5, name = "a5" });
list2.Add(new ClassB { Id = 1, name = "b1" });
list2.Add(new ClassB { Id = 2, name = "b2" });
list2.Add(new ClassB { Id = 4, name = "b4" });
list2.Add(new ClassB { Id = 5, name = "b5" });
// Goal is to set ClassA::myObjectB from List1 to
// matching instance (if any) of ClassB from List2
var query =
from a in list1
from b in list2
where a.Id == b.Id
select Tuple.Create(a, b);
foreach (var element in query)
element.Item1.myObjectB = element.Item2;
Update:
Or if you really don't want a for loop, I just realized you can use the fact that assignments return a value and at the same time make an entry in an obfuscated code contest :)
(from a in list1
from b in list2
where a.Id == b.Id
select a.myObjectB = b).ToList();
Update2:
I just thought of an alternate approach - depending on your scenario, a lazy mechanism might work for you?
public class ClassA
{
public int Id
public string name;
private ClassB myObjectB;
public ClassB MyObjectB {
get { return myObjectB ?? (myObjectB = list2.FirstOrDefault(x => this.Id == x.Id)); }
}
}
With classes defined as follows:
class ClassA {
public int Id { get; private set; }
public string name { get; private set; }
public ClassB myObjectB { get; set; }
public ClassA(int pId, string pName) {
Id = pId;
name = pName;
}
}
class ClassB {
public int Id { get; private set; }
public string name { get; private set; }
public ClassB(int pId, string pName) {
Id = pId;
name = pName;
}
}
You can do the following using the LINQ Join method:
var listA = new List<ClassA> {
new ClassA(1, "OneA"),
new ClassA(2, "TwoA"),
new ClassA(3, "ThreeA")
};
var listB = new List<ClassB> {
new ClassB(1, "OneB"),
new ClassB(2, "TwoB"),
new ClassB(4, "FourB")
};
listA
.Join(
listB,
itemA => itemA.Id,
itemB => itemB.Id,
(itemA, itemB) => new { ItemA = itemA, ItemB = itemB }
).ForEach(pair => pair.ItemA.myObjectB = pair.ItemB);
listA.ForEach(itemA => Console.WriteLine(
"{0} maps to {1}",
itemA == null
? "null"
: itemA.name,
(itemA == null || itemA.myObjectB == null)
? "null"
: itemA.myObjectB.name
));
Output is:
OneA maps to OneB
TwoA maps to TwoB
ThreeA maps to null
Related
I have three classes A, B, Common.
public class A
{
public int Id { get; set; }
public string Name { get; set; }
}
public class B
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Common
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
}
I want to put all A and B into a collection of Common
var result = new List<Common>();
foreach (var i in A_collection) {
result.Add(new Common() {
Id = i.Id,
Name = i.Name,
Type = "This is A"
});
}
foreach (var i in B_collection) {
result.Add(new Common() {
Id = i.Id,
Name = i.Name,
Type = "This is B"
});
}
Please notice that I cannot modify any code in class A, B and Common. But I can add new classes
Is there a way to use generics to simplify the code to just use one loop like this?
foreach (var i in T_collection) {
result.Add(new Common() {
Id = i.Id,
Name = i.Name,
Type = "This is T"
});
}
If your use case allows you to use interface in the places of the objects, and A and B are not sealed class, then you can wrap A and B in your own class with an interface. Something like this:
public interface ICommon
{
int Id { get; set; }
string Name { get; set; }
}
public class MyA : A, ICommon {}
public class MyB : B, ICommon {}
then instead of creating new instances of A or B, you can create instances of MyA and MyB, which will allow you to work with ICommon while still being interchangeable for A or B, or wherever you declare objects of A or B, just replace them with MyA or MyB.
Use LINQ?
var lA = new List<A>();
var lB = new List<B>();
var a = lA.Select(a => new Common(){ Id = a.Id, Name = a.Name, Type = "This is A"});
var b = lB.Select(b => new Common(){ Id = b.Id, Name = b.Name, Type = "This is B"});
var all = a.Append(b).ToList();
Not impossible in technical terms. First you write methods to create Common from object...
public static Common GenerateCommon(object o)
{
if (o is A)
{
return GenerateCommon(o as A);
}
if (o is B)
{
return GenerateCommon(o as B);
}
throw new Exception("Object is neither A nor B");
}
public static Common GenerateCommon(A a)
{
return new Common()
{
Id = a.Id,
Name = a.Name,
Type = "This is A"
};
}
public static Common GenerateCommon(B b)
{
return new Common()
{
Id = b.Id,
Name = b.Name,
Type = "This is B"
};
}
Then concatenate two lists as list of objects.
List<A> alist = new List<A>();
alist.Add(new A() { Id = 1, Name = "asd" });
alist.Add(new A() { Id = 2, Name = "2dd" });
alist.Add(new A() { Id = 3, Name = "3q" });
List<B> blist = new List<B>();
blist.Add(new B() { Id = 4, Name = "4asd" });
blist.Add(new B() { Id = 5, Name = "5dd" });
blist.Add(new B() { Id = 6, Name = "63q" });
List<object> olist = alist.Select(x => (object)x).ToList();
olist.AddRange(blist.Select(x => (object)x));
List<Common> clist = new List<Common>();
foreach(var o in olist)
{
clist.Add(GenerateCommon(o));
}
But I think modifying A/B/Common or following #T.S.'s answer is better.
I think I'd just write an extension method pair:
public static Common AsCommon(this A a)
{
return new Common()
{
Id = a.Id,
Name = a.Name,
Type = "This is A"
};
}
public static Common AsCommon(this B b)
{
return new Common()
{
Id = b.Id,
Name = b.Name,
Type = "This is B"
};
}
And then e.g.:
listOfCommon.AddRange(listOfA.Select(AsCommon));
Or e.g.:
listOfCommon = listOfA.Select(AsCommon).Concat(listOfB.Select(AsCommon)).ToList();
Probably also worth pointing out that using AutoMapper can save you the hassle of writing code to copy an A or B to a new Common, because automapper can be set up to understand A->Common and then copy the matching property names. With some extra config it can also copy mismatched properties
Let's say, we have an object definition like this,
class MyObject {
int id;
string name;
}
and we have a list of MyObjects.
List<MyObject> objectList
Now, I need to partition this objectList into 2 sub-lists based on which objects have a non-null name field.
So, after the operation, I need to have 2 lists, objectsWithName where name field is non-null and objectsWithoutName where name field is null (this is the immediate criteria, but I'm looking more towards partitioning into 2 groups using a predicate).
What is the simplest way to achieve this? Can I do this in a single operation? Using LINQ is permitted.
Two LINQ statements would do:
var nameNotNull = objectList.Where(o => !string.IsNullOrEmpty(o.Name));
var nameNull = objectList.Where(o => string.IsNullOrEmpty(o.Name));
Of course, you could use GroupBy, or a more efficient foreach statement.
To show the foreach option:
List<MyObject> nameNotNull = new List<MyObject>();
List<MyObject> nameNull = new List<MyObject>();
foreach (MyObject o in objectList)
{
if (!string.IsNullOrEmpty(o.Name))
{
nameNotNull.Add(o);
}
else
{
nameNull.Add(o);
}
}
public class MyObject
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyObjectLists
{
private readonly List<MyObject> _objects;
public List<MyObject> NullNameObjects
{
get
{
return _objects.Where(x => x.Name == null).ToList();
}
}
public List<MyObject> NonNullNameObjects
{
get
{
return _objects.Where(x => x.Name != null).ToList();
}
}
public MyObjectLists(List<MyObject> objects)
{
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
}
}
Using the code:
var list = new List<MyObject>
{
new MyObject
{
Id = 1,
Name = "John"
},
new MyObject
{
Id = 2
},
new MyObject
{
Id = 3,
Name = "Mary"
},
new MyObject
{
Id = 4
}
};
var objects = new MyObjectLists(list);
foreach (MyObject myObject in objects.NonNullNameObjects)
{
Console.WriteLine($"Object with Id {myObject.Id} has a non-null name");
}
foreach (MyObject myObject in objects.NullNameObjects)
{
Console.WriteLine($"Object with Id {myObject.Id} has a null name");
}
I think you're looking for something like this example:
class MyObject
{
int id;
string name;
}
var objectList = new List<MyObject>();
objectList.Add(new MyObject { name = "item 1" });
objectList.Add(new MyObject { name = string.Empty });
objectList.Add(new MyObject { name = "item 3" });
var objectsWithName = objectList.Where(x => !string.IsNullOrEmpty(x.name));
var objectsWithoutName = objectList.Except(objectsWithName);
I've created 2 lists that the first list contains the MyObject items with the name property is NOT null, otherwise to the second's.
Trying to figure out how to query an IEnumerable<T> using LINQ. The following simple example without IEnumerable works fine:
class Category
{
public string Title { get; set; }
public NameValue SubCategory { get; set; }
}
class NameValue
{
public string Name { get; set; }
public string Value { get; set; }
}
private static void testLinq()
{
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
foreach (Category c in q)
{
Console.WriteLine("{0} - {1}", c.Title, c.SubCategory.Name);
}
}
When I change the signature to have an IENumerable<NameValue> instead then I cannot access c.SubCategory.Name:
class Category
{
public string Title { get; set; }
public IEnumerable<NameValue> SubCategory { get; set; }
}
// For example, below does not compile:
IEnumerable<Category> q = categories.OrderBy(c => c.Title).ThenBy(c => c.SubCategory.Name);
// Also, this initialization of course won't work either
Category[] categories = {
new Category { Title ="Abc", SubCategory = new NameValue { Name = "A", Value = "5"} },
new Category { Title ="Xyz", SubCategory = new NameValue { Name = "B", Value = "10" } }
};
The error is:
IEnumerable' does not contain a definition for 'Name' and no extension method 'Name' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)
Do I need to do a cast of some sort?
Update:
Output should be something like:
Abc (category)
A (sub)
B (sub)
C (...)
Xyz
B
K
M
Xyz2
A
Q
Z
In SQL I would do like something like this:
SELECT c.Title, s.Name, s.Value FROM Categories c
INNER JOIN SubCategory s ON
c.CategoryID = s.CategoryID
ORDER BY c.Title, s.Name -- sorting first on Category.Title, then on SubCategory.Name
Your SubCategory will be a collection so you cannot do it using ThenBy. You need to order the Category(s) and then order their SubCategory(s) like this. Note I added two SubCategory(s) to the first Category but their order is not correct. After we order them, then they will be correct:
Category[] categories = {
new Category { Title ="Abc", SubCategory = new List<NameValue>
{ new NameValue { Name = "B", Value = "5"},
new NameValue { Name = "A", Value = "5"} } },
new Category { Title ="Xyz", SubCategory = new List<NameValue>
{ new NameValue { Name = "A", Value = "10" } } }};
// First order by categories
var cats = categories.OrderBy(c => c.Title)
// Then create a new category and order by sub categories
.Select(x => new Category { Title = x.Title,
SubCategory = x.SubCategory.OrderBy(y => y.Name) });
If you can get away with only sorting the children when you need to use them, sorting by the parent and then sorting the children upon use like this would be fairly efficient:
public void DisplayA(A value)
{
Console.WriteLine(value.Name);
foreach (var child in value.Children.OrderBy(c => c.Name))
{
Console.WriteLine(string.Format("- {0}", child.Name));
}
}
Or if you want to avoid that, you could add a sorted property to the class. Since it's Linq, it will only be evaluated when you iterate through the list.
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
public IEnumerable<B> SortedChildren { get { return Children.OrderBy(ca => ca.Name); } }
}
public class B
{
public string Name { get; set; }
}
If they don't work for you, you could try these, but they won't be so efficient since you're creating new objects.
// This will flatten it into a single object, sorted by one field and the the other. Since this is Linq, it will create these new flattened objects each time you iterate through the IEnumerable.
public IEnumerable<FlattenedA> GetSortedFlattened(IEnumerable<A> collection)
{
var flattened = collection.SelectMany(a => a.Children.Select(ca => new FlattenedA() { Name = a.Name, SubName = ca.Name }));
var sorted = flattened.OrderBy(f => f.Name).ThenBy(f => f.SubName);
return sorted;
}
// This will return objects of A, where the child enumerable has been replaced with an OrderBy. Again this will return new objects each time you iterate through. Only when you iterate through the children will they be sorted.
public IEnumerable<A> GetSortedNonFlattened(IEnumerable<A> collection)
{
var withSortedChildren = collection.Select(a => new A() { Name = a.Name, Children = a.Children.OrderBy(ca => ca.Name) });
var sorted = withSortedChildren.OrderBy(a => a.Name);
return sorted;
}
public class FlattenedA
{
public string Name { get;set }
public string SubName { get; set; }
}
public class A
{
public string Name { get; set; }
public IEnumerable<B> Children { get; set; }
}
when you are setting it as IEnumerable you can't do this
SubCategory = new NameValue { Name = "A", Value = "5"}
you should use some implementation of IEnumerable,
like List<>
so it should be something like this
SubCategory = new List<NameValue>{new NameValue { Name = "A", Value = "5"}, addmore here};
and for your order linq, i would do this,
var OrderedCategories = categories.select(g =>
new Category{ Name = g.Name, subcategories = g.subcategories.orderby(h => h.Name) });
That's because your SubCategory now is no longer a simple single instance of NameValue, but rather an enumeration of those. So now you need to specify how to .ThenBy over a collection of .Names.
Consider these two tables:
ClassID Name
1 C1
2 C2
ClassID List<CourseSession>
1 [Object that has value "A"], [Object that has value "B"]
2 [Object that has value "B"], [Object that has value "C"]
When I join these two tables in Linq, I get:
ID Name List
1 C1 [A, B]
2 C2 [A, B]
Wheras I need to expand them:
ID Name List
1 C1 A
1 C1 B
2 C2 A
2 C2 B
Linq code:
var classes = from row in t.AsEnumerable()
select new
{
ClassID = row.Field<Guid>("ClassID"),
ClassName = row.Field<string>("Name"),
};
var classCourses = from row in classes.AsEnumerable()
select new
{
ID = row.ID,
CourseSessionList = GetAllCoursesByID(row.ID).AsEnumerable()
};
//Attempt to join
var expandedClassCourse = from classRow in classes
join ccRow in classCourses
on classRow.ID equals ccRow.ID
into filteredExpandedClasses
select filteredExpandedClasses;
I'm not sure how to achieve this. Any ideas?
Something like (not sure what your model looks like):
context.CouseSessions.Where(cs => /* condition goes here */)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});
or
context.Classes.Where(c => /* condition goes here */)
.SelectMany(c => c.Courses)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});
I created two models based on assumption. I hope this helps.
class Info
{
public int Id { get; set; }
public string Name { get; set; }
public List<string> List { get; set; }
}
class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public string s { get; set; }
}
static void Main(string[] args)
{
var infos = new List<Info> { new Info { Id = 1, Name = "c1", List = new List<string> { "A", "B" } }, new Info { Id = 2, Name = "c2", List = new List<string> { "A", "B" } } };
var myClasses = new List<MyClass>();
foreach (var info in infos)
{
myClasses.AddRange(info.List.Select(a => new MyClass { Id = info.Id, Name = info.Name, s = a }));
}
}
(from c in classList
join s in sessionList on c.ClassID equals s.ClassID
select new
{
ID = c.ClassID,
Name = c.Name,
SessionList = s.SessionList
})
.SelectMany(e => e.SessionList.Select(s => new
{
ID = e.ClassID,
Name = e.Name,
Session = s
}))
Assuming
public class MyClass
{
public int ID {get; set; }
public string Name {get; set; }
}
and
List<MyClass> classList = //populate with MyClass instances of various IDs
I can do
List<MyClass> result = classList.FindAll(class => class.ID == 123);
and that will give me a list of just classes with ID = 123. Works great, looks elegant.
Now, if I had
List<List<MyClass>> listOfClassLists = //populate with Lists of MyClass instances
How do I get a filtered list where the lists themselves are filtered. I tried
List<List<MyClass>> result = listOfClassLists.FindAll
(list => list.FindAll(class => class.ID == 123).Count > 0);
it looks elegant, but doesn't work. It only includes Lists of classes where at least one class has an ID of 123, but it includes ALL MyClass instances in that list, not just the ones that match.
I ended up having to do
List<List<MyClass>> result = Results(listOfClassLists, 123);
private List<List<MyClass>> Results(List<List<MyClass>> myListOfLists, int id)
{
List<List<MyClass>> results = new List<List<MyClass>>();
foreach (List<MyClass> myClassList in myListOfLists)
{
List<MyClass> subList = myClassList.FindAll(myClass => myClass.ID == id);
if (subList.Count > 0)
results.Add(subList);
}
return results;
}
which gets the job done, but isn't that elegant. Just looking for better ways to do a FindAll on a List of Lists.
Ken
listOfClasses.SelectMany(x=>x).FindAll( /* yadda */)
Sorry about that, FindAll is a method of List<T>.
This
var result = from x in listOfClasses from y in x where SomeCondition(y) select y;
or
var result = listOfClasses.SelectMany(x=>x).Where(x=>SomeCondition(x));
To keep a list of lists, you could do something like this example:
MyClass a = new MyClass() { ID = 123, Name = "Apple" };
MyClass b = new MyClass() { ID = 456, Name = "Banana" };
MyClass c = new MyClass() { ID = 789, Name = "Cherry" };
MyClass d = new MyClass() { ID = 123, Name = "Alpha" };
MyClass e = new MyClass() { ID = 456, Name = "Bravo" };
List<List<MyClass>> lists = new List<List<MyClass>>()
{
new List<MyClass>() { a, b, c },
new List<MyClass>() { d, e },
new List<MyClass>() { b, c, e}
};
var query = lists
.Select(list => list.Where(item => item.ID == 123).ToList())
.Where(list => list.Count > 0).ToList();
query would be List<List<MyClass>> holding lists of MyClass objects that passed the test. At first glance, it looks out of order with the Where extension coming after the Select, but the transformation of the inner lists needs to occur first, and that's what's happening in the Select extension. Then it is filtered by the Where.
I would probably go with this
List<List<string>> stuff = new List<List<string>>();
List<List<string>> results = new List<List<string>>();
stuff.ForEach(list=> {var result = list.FindAll(i => i == "fun").ToList();
if (result.Count > 0) results.Add(result);
});
List<string> flatResult = new List<string>();
stuff.ForEach(List => flatResult.AddRange(List.FindAll(i => i == "fun")));
That way you can go with a jagged array or flatten it out.. But the Linq way works well too :-).
While producing a flat List<MyClass> will answer your need most of the time, the exact answer to your question is:
var result = (from list in ListOfClassLists
let listWithTheId=
(
(from myClass in list
where myClass.ID == id
select myClass)
.ToList()
)
where listWithTheId.Count > 0
select listWithTheId
).ToList();
This code snippet was taken from my Proof of Concept:
using System.Collections.Generic;
using System.Linq;
namespace ListOfListSelectionSpike
{
public class ListSpikeClass
{
public List<List<MyClass>> ListOfClassLists { get; set; }
private List<MyClass> list1, list2, list3;
public ListSpikeClass()
{
var myClassWithId123 = new MyClass("123");
var myClassWithIs345 = new MyClass("456");
list1 = new List<MyClass> { myClassWithId123, myClassWithIs345 };
list2 = new List<MyClass> { myClassWithId123, myClassWithIs345, myClassWithId123 };
list3 = new List<MyClass> { myClassWithIs345, myClassWithIs345 };
ListOfClassLists = new List<List<MyClass>> { list1, list2, list3 };
}
public List<List<MyClass>> GetListOfListsById(string id)
{
var result = (from list in ListOfClassLists
let listWithTheId =
((from myClass in list
where myClass.ID == id
select myClass)
.ToList())
where listWithTheId.Count > 0
select listWithTheId)
.ToList();
return result;
}
}
public class MyClass
{
public MyClass(string id)
{
ID = id;
Name = "My ID=" + id;
}
public string ID { get; set; }
public string Name { get; set; }
}
}