Basically I have a method that is passed a list of custom objects. I'm using C#. Each of these contains a name and another list of other custom objects, I'll call these subObjects. These each have a name and a list of strings. I need to loop through all the strings, but keep track of the names of the parent object and subject for error logging purposes. Is there a cleaner, nicer way to do this than nesting foreachs?
foreach (var obj in listOfObjects)
{
foreach (var subObj in obj.subObjects)
{
foreach (var aString in subObj.strings)
{
if (some condition applies)
{
//log error that includes obj, subObj, and subSubObj names, and aString.
}
}
}
}
You can write a LINQ query to get all error cases
var errors = from obj in listOfObjects
from subObj in obj.subObjects
from aString in subObj.strings
where /* your condition */
select new { obj, subObj, aString };
and than iterate over them only:
foreach(var errorCase in errors)
{
// log your error
}
or get the first one:
var error = errors.FirstOrDefault();
depending on your needs.
Adding to MarcinJuraszek answer, if linq to objects is preferred...
var errors = listOfObjects
.SelectMany(obj => obj.subObjects
.SelectMany(subObj => subObj.strings
.Where(r => /* your condition */)
.Select(aString => new { obj, subObj, aString })));
But there's nothing wrong with the code you posted. Clearly, yours is easier to follow at a quick glance.
Related
I have a c# class object that is List<int,string> I need to make sure the list's string variable is HtmlEncoded before passing back to an MVC controller (where I will later need to decode) but I'm having difficulty because the company's custom libraries are written in such a way that I have a hard time manipulating the code.
In this particular scenario, the data field is embedded into a SQL statement that is converted to the List<int,string> object. Before this List<int,string> is passed back, I need to use WebUtility.HtmlEncode on the string variable "GroupName":
public List<DatasetModel> GetMembers(List<int> groupIds)
{
using (var work in unitOfWorkFactory())
{
return work.DataContext.Query.Read<DatasetModel>(
$#SELECT {nameof(TableOne.GroupId)
, {(nameof(TableOne.Name))} as GroupName
FROM {this._tableProvider.GlobalTable<TableOne>()}
WHERE {nameof(TableOne.GroupId)} {groupIds.ToSqlInClause()}"
).ToList();
)
}
}
I've tried creating extension methods and something like the following but get type conversion errors, etc, that lead me to write rudimentary for loops and cast() and such that are not appropriate:
List<DatasetModel> returnList = model.Select(x => new DatasetModel{x.GroupId, WebUtility.HtmlEncode(x.Name)}).ToList();
FOLLOW-UP:
Here is what I've had to do to achieve the goal. I don't like it because I am using a foreach loop in the called method. I haven't been able to consistently understand / resolve errors involving 'initializer errors' and List vs generic lists:
public List<DatasetModel> GetMembers(List<int> groupIds)
{
using (var work in unitOfWorkFactory())
{
return EncodeList(work.DataContext.Query.Read<DatasetModel>(
$#SELECT {nameof(TableOne.GroupId)
, {(nameof(TableOne.Name))} as GroupName
FROM {this._tableProvider.GlobalTable<TableOne>()}
WHERE {nameof(TableOne.GroupId)} {groupIds.ToSqlInClause()}"
).ToList();
)
}
}
private static List<DatasetModel> EncodeList(List<DatasetModel> list)
{
var returnList = new List<DatasetModel>();
foreach (var l in list)
{
var m = new DatasetModel(attributeId: l.attributeId, attributeName: WebUtility.HtmlEncode(l.attributeName));
returnList.Add(m);
}
return returnList;
}
I have an object that I'm returning from a third party so I need it to be dynamic (see IEnumerable requirements below). I want to return the object as Json with all of its child nodes.
I have two questions
how can I do a .Select on the dynamic object (c.Nodes) (is this
the way to go?)
How can I keep looping through the nodes until it's done.
This is the code I wrote so far but now .Select( c =>... is giving an error
cannot use a lambda expression as an argument to a dynamically
dispatched...
IEnumerable<dynamic> requirements = _Requirements.GetRequirements();
return Json(new
{
Requirements = requirements.Select(r => new
{
r.Text,
r.Number,
nodes = r.nodes.Select(c => new
{
c.Text,
c.Number,
nodes = c.Nodes
//and overhere keep looping through the nodes until it's done
})
})
});
The reason you get this compile error is because your inner collection of r.node is a dynamic type and thus the extension methods can't be found for it.
Your case is similar to this question. Instead what you should do is:
nodes = ((IEnumerable<dynamic>)r.nodes)
.Select(c => new
{
c.Text,
c.Number,
nodes = c.Nodes
//and overhere keep looping through the nodes until it's done
})
If you want to be sure that it won't get an exception when casting you can do
r.nodes is IEnumerable<dynamic> ? /*the select*/ : Enumerable.Empty<dynamic>
For second question, if I properly understood what you meant then you can write a recursive method that gets a dynamic and then checks if it has a nodes property which is an IEnumerable<dynamic> and if so recursively calls itself foreach node in the nodes. Something along the general idea of:
public static IEnumerable<dynamic> SomeName (dynamic input)
{
var collection = input as IEnumerable<dynamic>;
if (collection == null)
return Enumerable.Empty<dynamic>();
foreach (var item in collection)
{
/*recursive part*/
}
return collection;
}
(Haven't tested - just quick concept writing)
So I have two collections - one of int?, and one of string.
I want to iterate over both, and I have a good chunk of code that I want to perform to each. However there are some slight differences. if the value is an int?, some additional code is needed.
I'm trying to follow DRY principles, so I don't want to rewrite my code. I also don't want to put it in an external function, as this is already abstracted away from my MVC app controller code and I don't want it getting needlessly complex.
Is there a way to do something like this in c#
foreach(object o in (ints && strings))
{
if(o is int)
{
// do in specific stuff
}
// do all my generic stuff.
}
EDIT:
It's actually int? not int. when I try and combine them into a generic list of type List<object> I get an error
With this particular structure, you can combine them into one big object collection using Concat, like:
foreach (object o in ints.Cast<object>().Concat(strings.Cast<object>()))
But I'd probably suggest breaking out your functionality into a method, and running two loops:
foreach (int i in ints) {
DoSomething(i);
DoSomethingWithInt(i);
}
foreach (string s in strings) {
DoSomething(s);
DoSomethingWithString(s);
}
That will keep things clean and a little more readable in my opinion.
I think it would be cleaner to put the generic stuff in a common function, then loop over both collections. That way you get rid of the somewhat odd if int stuff.
foreach(int number in ints)
{
// do int specific stuff
GeneralFunction(number);
}
foreach(string stringValue in strings)
{
GeneralFunction(number);
}
This could also work for you:
IEnumerator en1 = ints.GetEnumerator();
IEnumerator en2 = strings.GetEnumerator();
while((en1.MoveNext()) && (en2.MoveNext()))
{
WhateverActionWithNullableInt((int?)en1.Current);
WhateverActionWithString((string)en2.Current);
}
Yes, you can do this:
List<int> ints = new List<int>();
List<string> strings = new List<string>();
foreach (object o in ints.Cast<object>().Concat(strings))
{
if (o is int)
{
// do in specific stuff
}
// do all my generic stuff.
}
I would separate it into functions and take advantage of overloading myself, but you said you didn't want to do that.
You should create a new list, which is the concatenation of the two lists :
EDIT
List<object> list = new List<object>();
List<int> ints = new List<int>(); ints.Add(74); ints.Add(47);
List<string> strings = new List<string>(); strings.Add("hello"); strings.Add("hello2");
list.AddRange(ints.Cast<object>());
list.AddRange(strings.Cast<object>());
foreach (object o in list)
{
Trace.WriteLine(o);
if (o is int)
{
// do in specific stuff
}
// do all my generic stuff.
}
I currently working on an dynamic upload module. The idea is to only define the file and the data contract for each new file. Currently I'm using reflection with 2 foreach, this is some heavy code to do this. As you can see in the code I have my object containing the csv file and 2 other lists. These two lists contains all the properties of the object where I would like to do data validation on.
var myCustomObjects = CsvSettings(new CsvReader(readFile, config)).GetRecords<MyCustomObject>();
var decimalProprties = GetPropertyNames<MyCustomObject>(typeof(decimal)).ToList();
var dateProprties = GetPropertyNames<MyCustomObject>(typeof(DateTime)).ToList();
foreach (var myCustomObject in myCustomObjects)
{
foreach (var dateProperty in dateProprties)
{
var value = myCustomObject.GetType().GetProperty(dateProperty).GetValue(myCustomObject, null);
Console.WriteLine(value); //code to check and report the value
}
Console.WriteLine(myCustomObject.Een + "|" + myCustomObject.Twee + "|" + myCustomObject.Drie);
}
How can I do this with an expression or even another way to have so less heavy code?
The code seems fine as-is. You could perhaps simplify it a little by using a method that returns Key/Value pairs for all public properties of a certain type, like so (error handling elided for brevity):
public static IEnumerable<KeyValuePair<string, T>> PropertiesOfType<T>(object myObject)
{
var properties =
from property in myObject.GetType().GetProperties()
where property.PropertyType == typeof(T) && property.CanRead
select new KeyValuePair<string, T>(property.Name, (T)property.GetValue(myObject));
return properties;
}
Then you can avoid the additional call to GetProperty() in your inner loop:
foreach (var myCustomObject in myCustomObjects)
{
foreach (var dateProperty in PropertiesOfType<DateTime>(myCustomObject))
{
Console.WriteLine(dateProperty.Value); // code to check and report the value.
}
}
Also note that you don't seem to need the .ToList() calls.
I was wondering, if there is an extension method that allow me to iterate a List and let me do the same thing with every item in the list. For example:
.RemoveAll(x => x.property == somevalue)
This removes every element wichs fulfill the condition. But, what if I have this:
foreach(object item in lstObjects)
{
object MyObject = new object();
MyObject.propertyone = item.property
MyObject.propertytwo = somevalue;
anotherlist.Add(MyObject);
}
Of course, the real code is a little more complex than this. My objective is to, instead of a foreach use an extension method, I have found List<T>.ForEach() but I can't get it to work, and this method does not exist in a var list. I found too .Select<>, .Where<> but this returns values, and in my method there is no need to return any value.
var convertedItems = lstObjects.Select(item =>
{
object MyObject = new object();
MyObject.propertyone = item.property
MyObject.propertytwo = somevalue;
return MyObject;
});
anotherList.AddRange(convertedItems);
or
anotherList = convertedItems.ToList();
and if you want to make it shorter:
var convertedItems = lstObjects.Select(item =>
new object {propertyone = item.property, propertytwo = somevalue});
I'm not sure I see why you want an extension method here. List<T>.ForEach() will do mostly what you like but your existing foreach loop is both idiomatic and readable. Is there a reason that you can't just write a normal function to do this?
public void DoMyThing(IList<object> objects) {
foreach (var obj in objects) {
someOtherList.Add(new MyObj() {
item1 = obj
});
}
}
In general if you find that you need to mutate items and not return values you don't want to use LINQ or query operators. Just use a foreach.
Edit: The answers suggesting Select() would work for this simple code, however you state
the real code is a little more complex than this
Which suggests to me that you may have to mutate some other state during iteration. The Select method will defer this mutation until the sequence is materialized; this will probably give you strange results unless you're familiar with how LINQ queries defer execution and capture outer variables.
It's trivial to write your own ForEach extension. I include the following in all of my code:
public static void ForEach<T>(this IEnumerable<T> collection, Action<T> action )
{
foreach (T item in collection)
{
action(item);
}
}
You can accomplish this via a Select statement:
var newList = lstObjects.Select(o =>
new { propertyone = o.property,
propertytwo = somevalue }).ToList();
Here is how you use ForEach with a lambda expression:
lstObjects.ForEach(item =>
{
MyObject obj = new MyObject();
obj.propertyone = item.property;
obj.propertytwo = somevalue;
anotherlist.Add(obj);
});
However as you can see it looks remarkably similar to what you already have!
Alternatively it looks to me like Select might be a better match for what you want to do:
anotherList.AddRange(lstObjects.Select(item => new MyObject()
{
propertyone = item.property,
obj.propertytwo = somevalue,
}));
List<MyObjectType> list = new List<MyObjectType>();
list.ForEach((MyObjectType item) => {
object MyObject = new object()
{
MyObject.propertyone = item.property,
MyObject.propertytwo = somevalue
};
anotherlist.Add(MyObject);
});
If you want to perform an action as part of an iteration, you might want to consider the .Do method which is part of the Interactive Extensions. See http://www.thinqlinq.com/Post.aspx/Title/Ix-Interactive-Extensions-return.
You can easily create an extension method to do this:
public IEnumerable<T> RemoveAll(this List<T> list, Func<bool, T> condition)
{
var itemsToRemove = list.Where(s => condition(s));
list.RemoveAll(itemsToRemove);
}
and you could then call it like this:
myList.RemoveAll(x => x.Property == someValue);
Edit: Here is another method for doing the same.
As far as 'built-in' goes there is no .ForEach(); however I think .Aggregate() would be the most appropriate option here (if you absolutely and utterly want a built-in function).
lstObjects.Aggregate(anotherList, (targetList, value) =>
{
object MyObject = new object();
MyObject.propertyone = item.property
MyObject.propertytwo = somevalue;
targetList.Add(MyObject);
return targetList;
});
You can obviously just write your own extension methods:
public static IEnumerable<T> Intercept<T>(this IEnumerable<T> values, Action<T> each)
{
foreach (var item in values)
{
each(item);
yield return item;
}
}
public static IEnumerable<T> Intercept<T>(this IEnumerable<T> values, Action<T, int> each)
{
var index = 0;
foreach (var item in values)
{
each(item, index++);
yield return item;
}
}
// ...
a.Intercept(x => { Console.WriteLine(x); }).Count();
NB: The reason I don't create a ForEach like everyone else, is because Microsoft didn't include it because, by design Linq methods always return a value, or list of values.
Specifically to your question, .Select<T> will do the trick.
anotherList.AddRange(lstObjects.Select(x => new MyObject()
{
propertyone = x.property,
propertytwo = somevalue
}));