Combining two list variables into one operation - c#

What I have that I start with is three string variables for example
string myVar1 = "hi";
string myVar2 = "bye";
string myVar3 = "who dis";
CURRENTLY I am doing this, It "works" but I feel like there is redundancy:
List<string> listOne= new List<string>
{
myVar1 , myVar2,myVar3
};
listOne= listOne.Where(t => !string.IsNullOrWhiteSpace(t)).Select(t => t).ToList();
var whatIFinallyWantToHave= new HashSet<string>(listOne, StringComparer.OrdinalIgnoreCase);
I am thinking there should be a way to get rid of listOne and do it in one go. Do you know how? ( note we still need to ignore the empty or null strings that listOne is cleaning.)

I think it's worth adding an extension in this case so you can chain from the list to the set directly. This way you don't have to break the flow to explicitly construct the hash set. Perhaps:
public static class Extensions
{
public static HashSet<T> ToHashSet<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer = null)
{
return new HashSet<T>(source, comparer);
}
}
And use it like so:
string myVar1 = "hi";
string nully = null;
string myVar2 = "bye";
string empty = "";
string myVar3 = "who dis";
string dup = "WHO DIS";
List<string> listOne = new List<string>
{
nully, myVar1, empty, myVar2 ,myVar3, dup
};
listOne
.Where(t => !string.IsNullOrWhiteSpace(t))
.ToHashSet(StringComparer.OrdinalIgnoreCase)
.ToList()
.ForEach(Console.WriteLine);
// hi
// bye
// who dis

There is no reason to create a List or have a separate variable, but you have to combine the individual values somehow. Also, there is no reason to create (yet) another List just because you are filtering.
string myVar1 = "hi";
string myVar2 = "bye";
string myVar3 = "who dis";
var whatIFinallyWantToHave = new[] { myVar1, myVar2, myVar3 }
.Where(s => !String.IsNullOrWhiteSpace(s))
.ToHashSet(StringComparer.OrdinalIgnoreCase);
NOTE: This requires .Net 4.7.2 for the ToHashSet method.

Related

How to get item type and members from list of object?

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.

Linq expression to join a list and a dictionary

I have a list of part numbers:
var parts = new List<string> {"part1", "part2", "part3"};
I also have a dictionary of quantities for these part numbers:
var quantities = new Dictionary<string, int> {{"part1", 45}, {"part3", 25}};
Given a delimiter of |, I need to arrange these values in a flat file like so:
SalesRep|part1|part2|part3
Mr. Foo|45||25
What I'd like to do is define a string that no matter what values are in parts and quantities, I can tack this on to the sales rep name to resemble the example above.
It seems like I should be able to do this with a string.Join() on an enumerable LINQ operation, but I can't figure out what statement will get me the IEnumerable<string> result from joining parts and quantities. It thought that would be a .Join(), but the signature doesn't seem right. Can someone enlighten me?
Something like this perhaps?
var partValues = parts.Select(x => quantities.ContainsKey(x) ? quantities[x] : 0);
Basically for each item in the parts list you either pick the value from your dictionary, or if it doesn't exist 0.
To make this a little more interesting you could define a generic extension method on IDictionary<T,U> that makes this a little more readable:
public static class DictionaryExtensions
{
public static U GetValueOrDefault<T,U>(this IDictionary<T, U> dict, T key)
{
if(dict.ContainsKey(key))
{
return dict[key];
}
return default(U);
}
}
Then you can simply write:
var partValues = parts.Select(quantities.GetValueOrDefault);
var parts = new List<string> { "part1", "part2", "part3" };
var quantities = new Dictionary<string, int> { { "part1", 45 }, { "part3", 25 } };
var result = string.Join("|",
from p in parts select quantities.ContainsKey(p)
? quantities[p].ToString() : "");

Sort List without creating new variable

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();

Iterate over object fields in C#

Foo is a class with a lot of string fields. I want to create a method Wizardify that performs an operation on many of the fields of the object. I could do it like this:
Foo Wizardify(Foo input)
{
Foo result;
result.field1 = Bar(input.field1);
result.field2 = Bar(input.field2);
result.field3 = Bar(input.field3);
...
This is some easily generated code, but I prefer not to waste fifty lines on this. Is there a way to go over selected fields of an object? Note that there are four or five fields I want to work on in a different way and they should be excluded from the iteration.
try
foreach ( FieldInfo FI in input.GetType().GetFields () )
{
FI.GetValue (input)
FI.SetValue (input, someValue)
}
Though I would not recommend the reflection approach for known Types - it is slow and depending on your specific scenario could pose some permission issue at runtime...
This is what I have - it gives me a list (names) of all properties in my classes, that later I can work on with Reflection or "Expression trees":
private static string xPrev = "";
private static List<string> result;
private static List<string> GetContentPropertiesInternal(Type t)
{
System.Reflection.PropertyInfo[] pi = t.GetProperties();
foreach (System.Reflection.PropertyInfo p in pi)
{
string propertyName = string.Join(".", new string[] { xPrev, p.Name });
if (!propertyName.Contains("Parent"))
{
Type propertyType = p.PropertyType;
if (!propertyType.ToString().StartsWith("MyCms"))
{
result.Add(string.Join(".", new string[] { xPrev, p.Name }).TrimStart(new char[] { '.' }));
}
else
{
xPrev = string.Join(".", new string[] { xPrev, p.Name });
GetContentPropertiesInternal(propertyType);
}
}
}
xPrev = "";
return result;
}
public static List<string> GetContentProperties(object o)
{
result = new List<string>();
xPrev = "";
result = GetContentPropertiesInternal(o.GetType());
return result;
}
Usage: List<string> myProperties = GetContentProperties(myObject);
Loop through typeof(YourType).GetProperties() and call GetValue or SetValue.
Note that reflection is rather slow.
You could use the Dynamic Language Runtime to generate a lambda of the type Func. You'll just need to generate the lambda once (you can cache it away) and there'll be no reflection performance hit.

Concat all strings inside a List<string> using LINQ

Is there any easy LINQ expression to concatenate my entire List<string> collection items to a single string with a delimiter character?
What if the collection is of custom objects instead of string? Imagine I need to concatenate on object.Name.
string result = String.Join(delimiter, list);
is sufficient.
Warning - Serious Performance Issues
Though this answer does produce the desired result, it suffers from poor performance compared to other answers here. Be very careful about deciding to use it
By using LINQ, this should work;
string delimiter = ",";
List<string> items = new List<string>() { "foo", "boo", "john", "doe" };
Console.WriteLine(items.Aggregate((i, j) => i + delimiter + j));
class description:
public class Foo
{
public string Boo { get; set; }
}
Usage:
class Program
{
static void Main(string[] args)
{
string delimiter = ",";
List<Foo> items = new List<Foo>() { new Foo { Boo = "ABC" }, new Foo { Boo = "DEF" },
new Foo { Boo = "GHI" }, new Foo { Boo = "JKL" } };
Console.WriteLine(items.Aggregate((i, j) => new Foo{Boo = (i.Boo + delimiter + j.Boo)}).Boo);
Console.ReadKey();
}
}
And here is my best :)
items.Select(i => i.Boo).Aggregate((i, j) => i + delimiter + j)
Note: This answer does not use LINQ to generate the concatenated string. Using LINQ to turn enumerables into delimited strings can cause serious performance problems
Modern .NET (since .NET 4)
This is for an array, list or any type that implements IEnumerable:
string.Join(delimiter, enumerable);
And this is for an enumerable of custom objects:
string.Join(delimiter, enumerable.Select(i => i.Boo));
Old .NET (before .NET 4)
This is for a string array:
string.Join(delimiter, array);
This is for a List<string>:
string.Join(delimiter, list.ToArray());
And this is for a list of custom objects:
string.Join(delimiter, list.Select(i => i.Boo).ToArray());
using System.Linq;
public class Person
{
string FirstName { get; set; }
string LastName { get; set; }
}
List<Person> persons = new List<Person>();
string listOfPersons = string.Join(",", persons.Select(p => p.FirstName));
Good question. I've been using
List<string> myStrings = new List<string>{ "ours", "mine", "yours"};
string joinedString = string.Join(", ", myStrings.ToArray());
It's not LINQ, but it works.
You can simply use:
List<string> items = new List<string>() { "foo", "boo", "john", "doe" };
Console.WriteLine(string.Join(",", items));
Happy coding!
I think that if you define the logic in an extension method the code will be much more readable:
public static class EnumerableExtensions {
public static string Join<T>(this IEnumerable<T> self, string separator) {
return String.Join(separator, self.Select(e => e.ToString()).ToArray());
}
}
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString() {
return string.Format("{0} {1}", FirstName, LastName);
}
}
// ...
List<Person> people = new List<Person>();
// ...
string fullNames = people.Join(", ");
string lastNames = people.Select(p => p.LastName).Join(", ");
List<string> strings = new List<string>() { "ABC", "DEF", "GHI" };
string s = strings.Aggregate((a, b) => a + ',' + b);
I have done this using LINQ:
var oCSP = (from P in db.Products select new { P.ProductName });
string joinedString = string.Join(",", oCSP.Select(p => p.ProductName));
Put String.Join into an extension method. Here is the version I use, which is less verbose than Jordaos version.
returns empty string "" when list is empty. Aggregate would throw exception instead.
probably better performance than Aggregate
is easier to read when combined with other LINQ methods than a pure String.Join()
Usage
var myStrings = new List<string>() { "a", "b", "c" };
var joinedStrings = myStrings.Join(","); // "a,b,c"
Extensionmethods class
public static class ExtensionMethods
{
public static string Join(this IEnumerable<string> texts, string separator)
{
return String.Join(separator, texts);
}
}
This answer aims to extend and improve some mentions of LINQ-based solutions. It is not an example of a "good" way to solve this per se. Just use string.Join as suggested when it fits your needs.
Context
This answer is prompted by the second part of the question (a generic approach) and some comments expressing a deep affinity for LINQ.
The currently accepted answer does not seem to work with empty or singleton sequences. It also suffers from a performance issue.
The currently most upvoted answer does not explicitly address the generic string conversion requirement, when ToString does not yield the desired result. (This can be remedied by adding a call to Select.)
Another answer includes a note that may lead some to believe that the performance issue is inherent to LINQ. ("Using LINQ to turn enumerables into delimited strings can cause serious performance problems.")
I noticed this comment about sending the query to the database.
Given that there is no answer matching all these requirements, I propose an implementation that is based on LINQ, running in linear time, works with enumerations of arbitrary length, and supports generic conversions to string for the elements.
So, LINQ or bust? Okay.
static string Serialize<T>(IEnumerable<T> enumerable, char delim, Func<T, string> toString)
{
return enumerable.Aggregate(
new StringBuilder(),
(sb, t) => sb.Append(toString(t)).Append(delim),
sb =>
{
if (sb.Length > 0)
{
sb.Length--;
}
return sb.ToString();
});
}
This implementation is more involved than many alternatives, predominantly because we need to manage the boundary conditions for the delimiter (separator) in our own code.
It should run in linear time, traversing the elements at most twice.
Once for generating all the strings to be appended in the first place, and zero to one time while generating the final result during the final ToString call. This is because the latter may be able to just return the buffer that happened to be large enough to contain all the appended strings from the get go, or it has to regenerate the full thing (unlikely), or something in between. See e.g. What is the Complexity of the StringBuilder.ToString() on SO for more information.
Final Words
Just use string.Join as suggested if it fits your needs, adding a Select when you need to massage the sequence first.
This answer's main intent is to illustrate that it is possible to keep the performance in check using LINQ. The result is (probably) too verbose to recommend, but it exists.
You can use Aggregate, to concatenate the strings into a single, character separated string but will throw an Invalid Operation Exception if the collection is empty.
You can use Aggregate function with a seed string.
var seed = string.Empty;
var seperator = ",";
var cars = new List<string>() { "Ford", "McLaren Senna", "Aston Martin Vanquish"};
var carAggregate = cars.Aggregate(seed,
(partialPhrase, word) => $"{partialPhrase}{seperator}{word}").TrimStart(',');
you can use string.Join doesn’t care if you pass it an empty collection.
var seperator = ",";
var cars = new List<string>() { "Ford", "McLaren Senna", "Aston Martin Vanquish"};
var carJoin = string.Join(seperator, cars);

Categories

Resources