what does the Select method do on a c# int array - c#

I'm a beginner at C#.
I have come across this piece of code:
List<RelatedProductViewModel> relatedProductViewModel = vm.RelatedProductID.Select(p => new RelatedProductViewModel { RelatedProductID = p, ProductID = vm.ProductID, Type = vm.Type }).ToList();
vm.RelatedProductID is an int array with just one element (0) with a value of 8666.
So I'm thinking that Select is a method available on int arrays, but I can't find any documentation on this.
Can anybody enlighten me:
What does Select do?
Ultimately, I'm trying to figure out what value is being used for p in the llambda expression - please don't get too technical about llambda expression as I have only just read about them.

Select creates a projection; meaning, you provide a function that takes an int and outputs a something, then for every int in the array it executes the function. It is basically this:
static IEnumerable<TResult> Select<TSource,TResult>(
this IEnumerable<TSource> source, Func<TSource,TResult> selector)
{
foreach(TSource item in source)
yield return selector(source);
}
In your case, the selector for each is:
p => new RelatedProductViewModel { RelatedProductID = p,
ProductID = vm.ProductID, Type = vm.Type }
Meaning: each value p in the array become the RelatedProductID of a new RelatedProductViewModel, with a ProductID and Type set via the "captured" value of vm.
So the output in your example will be a list of length 1, where that single item has a RelatedProductID of 8666.

Select is part of IEnumerable<T>. Arrays automatically implement IEnumerable<T> when they are created, so since it implements it, the .Select() is available from there.
But Select is projecting the results from one set to another. It is returning 1 result per iterated value.
When using .Select on an IQueryable<T>, internally it may work slightly differently. It retrieves them as they are iterated, much like a yield does.
By using ToList() at the end, you are forcing it to enumerate all results in one go. For in memory types this is quick, but for databases or resources that have a latency in retrieving items, this can have a large performance impact.

The code is equivalent (but not exactly the same as the following code):
List<RelatedProductViewModel> relatedProductViewModel
= new List<RelatedProductViewModel>();
foreach (var p in vm.RelatedProductID) {
relatedProductViewModel.Add(
new RelatedProductViewModel {
RelatedProductID = p,
ProductID = vm.ProductID,
Type = vm.Type
}
);
}
The Select projects the items in the source sequence (relatedProductViewModel) into a new sequence of elements. The projected sequence is then converted to a list by ToList() and stored in relatedProductViewModel.
As you can see, using LINQ (the original code) creates much cleaner code.

Related

is it possible to subset list of custom class objects based on a parameter of these objects? (without LINQ)

What I am trying to achieve in C# without using LINQ is to subset a list of custom-class objects based on the value of one of the parameters of these objects.
Let's say the declaration of my list is the following:
List<MyCustom> listofobj = new List<MyCustom>();
Also, assume that the custom-class object "MyCustom" can return two parameters: MyCustom.name and MyCustom.age
Is there a way trough which I can retrieve and save into a temporary new list (let's call it "templist") the subset of the original list (i.e. "listofobj") formed by all its MyCustom objects that have "age" parameter greater than 30? Thanks!
Sure. You can reinvent LINQ:
IEnumerable<MyCustom> WhereByAge(IEnumerable<MyCustom> source, int age)
{
foreach (MyCustom myCustom in source)
{
if (myCustom.Age > age)
{
yield return myCustom;
}
}
}
then:
List<MyCustom> filteredList = new List(WhereByAge(listofobj, 30));
But why? I strongly recommend that if you want LINQ-like behavior, just use LINQ.
This is how you get a subset of original list based on parameters without linq.
List<MyCustom> listofobj = new List<MyCustom>();
List<MyCustom> templist = new List<MyCustom>();
foreach(var obj in listofobj)
{
if(obj.Age > 30)
{
templist.Add(obj);
}
}
You can also use this Linq.
templist = listofobj.Where(obj => obj.Age > 30).ToList();
Linq is slower than a normal code in general. but this should not be problem if this is not hot path in your code. performance difference is negligible.

matching string to item ids in LINQ

I have a string:
strCheckedCategories = "2;"
an EntityList representing a SharePoint list, with item IDs from 1 to 21:
EntityList<VendorSearchesItem> vendorSearches =
dataContext.GetList<VendorSearchesItem>("Vendor Searches");
a LINQ query returning fields from two SharePoint lists that are joined to the "Vendor Searches" list:
var vendorSearchesQuery = (from s in vendorSearches
orderby s.VendorID.Title
select new
{
Vendor = s.VendorID.Title,
Website = s.VendorID.VendorWebsite,
VendorID = s.VendorID.Id,
SearchType = s.SearchTypeID.Title,
SearchTypeId = s.SearchTypeID.Id
});
and another LINQ query returning only the items where the item ID is in the list:
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Contains(m2.SearchTypeId.ToString())
select m2
The problem is that, in addition to returning the item with ID 2 (desired result) the query also returns items with ID 12, 20, and 21. How can I fix that?
So, fundamentally, what you want to do here is have an IN clause in which you specify a bunch of values for a field and you want rows who's value for that column is in that set.
CAML does actually have an IN clause which you could use, but sadly LINQ to Sharepoint doesn't provide any means of generating an IN clause; it's simply not supported by the query provider.
You're trying to use a bit of a hack to get around that problem by trying to do a string comparison rather than using the proper operators, and you're running into the pitfals of stringifying all of your operations. It's simply not well suited to the task.
Since, as I said, you cannot get LINQ to SharePoint to use an IN, one option would simply be to not use LINQ, build the CAML manually, and execute it using the standard server object model. But that's no fun.
What we can do is have a series of OR checks. We'll see if that column value is the first value, or the second, or the third, etc. for all values in your set. This is effectively identical to an IN clause, it's just a lot more verbose.
Now this brings us to the problem of how to OR together an unknown number of comparisons. If it were ANDs it'd be easy, we'd just call Where inside of a loop and it would AND those N clauses.
Instead we'll need to use expressions. We can manually build the expression tree ourselves of a dynamic number of OR clauses and then the query provider will be able to parse it just fine.
Our new method, WhereIn, which will filter the query to all items where a given property value is in a set of values, will need to accept a query, a property selector of what property we're using, and a set of values of the same type to compare it to. After we have that it's a simple matter of creating the comparison expression of the property access along with each key value and then ORing all of those expressions.
public static IQueryable<TSource> WhereIn<TSource, TKey>(
this IQueryable<TSource> query,
Expression<Func<TSource, TKey>> propertySelector,
IEnumerable<TKey> values)
{
var t = Expression.Parameter(typeof(TSource));
Expression body = Expression.Constant(false);
var propertyName = ((MemberExpression)propertySelector.Body).Member.Name;
foreach (var value in values)
{
body = Expression.OrElse(body,
Expression.Equal(Expression.Property(t, propertyName),
Expression.Constant(value)));
}
return query.Where(Expression.Lambda<Func<TSource, bool>>(body, t));
}
Now to call it we just need the query, the property we're filtering on, and the collection of values:
var q2 = vendorSearchesQuery.WhereIn(vendor => vendor.SearchTypeId
, strCheckedCategories.Split(';'));
And voila.
While I'd expect that to work as is, you may need to call the WhereIn before the Select. It may not work quite right with the already mapped SearchTypeId.
You should probably use a Regex, but if you want a simpler solution then I would avoid string searching and split those strings to an array:
string strCheckedCategories = "2;4;5;7;12;16;17;19;20;21;";
string[] split = strCheckedCategories.Split(';');
It will create an empty entry in the array for the trailing semicolon delimiter. I would check for that and remove it if this is a problem:
strCheckedCategories.TrimEnd(';');
Finally now you can change your where clause:
where split.Contains(m2.SearchTypeId.ToString())
If you have a very large list it is probably worth comparing integers instead of strings by parsing strCheckedCategories into a list of integers instead:
int[] split = strCheckedCategories.Split(';').Select(x => Convert.ToInt32(x)).ToArray();
Then you can do a quicker equality expression:
where split.Contains(m2.SearchTypeId)
try:
strCheckedCategories.Split(new []{';'}).Any(x => x == m2.SearchTypeId.ToString())
Contains will do a substring match. And "20" has a substring "2".
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Split(';').Contains(m2.SearchTypeId.ToString())
select m2
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Contains(";" + m2.SearchTypeId + ";")
select m2
And your strCheckedCategories should always end with ; and start with ;, for example ;2;, ;2;3;, ...
NOTE: This trick works only when your SearchTypeId should always not contain ;. I think you should use another kind of separator like \n or simply store your checked categories in a list or some array. That's the more standard way to do.

C# Sort List Based on Another List

I have a class that has multiple List<> contained within it. Its basically a table stored with each column as a List<>. Each column does not contain the same type. Each list is also the same length (has the same number of elements).
For example:
I have 3 List<> objects; one List, two List, and three List.
//Not syntactically correct
List<DateTime> one = new List...{4/12/2010, 4/9/2006, 4/13/2008};
List<double> two = new List...{24.5, 56.2, 47.4};
List<string> three = new List...{"B", "K", "Z"};
I want to be able to sort list one from oldest to newest:
one = {4/9/2006, 4/13/2008, 4/12/2010};
So to do this I moved element 0 to the end.
I then want to sort list two and three the same way; moving the first to the last.
So when I sort one list, I want the data in the corresponding index in the other lists to also change in accordance with how the one list is sorted.
I'm guessing I have to overload IComparer somehow, but I feel like there's a shortcut I haven't realized.
I've handled this design in the past by keeping or creating a separate index list. You first sort the index list, and then use it to sort (or just access) the other lists. You can do this by creating a custom IComparer for the index list. What you do inside that IComparer is to compare based on indexes into the key list. In other words, you are sorting the index list indirectly. Something like:
// This is the compare function for the separate *index* list.
int Compare (object x, object y)
{
KeyList[(int) x].CompareTo(KeyList[(int) y])
}
So you are sorting the index list based on the values in the key list. Then you can use that sorted key list to re-order the other lists. If this is unclear, I'll try to add a more complete example when I get in a situation to post one.
Here's a way to do it using LINQ and projections. The first query generates an array with the original indexes reordered by the datetime values; in your example, the newOrdering array would have members:
{ 4/9/2006, 1 }, { 4/13/2008, 2 }, { 4/12/2010, 0 }
The second set of statements generate new lists by picking items using the reordered indexes (in other words, items 1, 2, and 0, in that order).
var newOrdering = one
.Select((dateTime, index) => new { dateTime, index })
.OrderBy(item => item.dateTime)
.ToArray();
// now, order each list
one = newOrdering.Select(item => one[item.index]).ToList();
two = newOrdering.Select(item => two[item.index]).ToList();
three = newOrdering.Select(item => three[item.index]).ToList();
I am sorry to say, but this feels like a bad design. Especially because List<T> does not guarantee element order before you have called one of the sorting operations (so you have a problem when inserting):
From MSDN:
The List is not guaranteed to be
sorted. You must sort the List
before performing operations (such as
BinarySearch) that require the List
to be sorted.
In many cases you won't run into trouble based on this, but you might, and if you do, it could be a very hard bug to track down. For example, I think the current framework implementation of List<T> maintains insert order until sort is called, but it could change in the future.
I would seriously consider refactoring to use another data structure. If you still want to implement sorting based on this data structure, I would create a temporary object (maybe using an anonymous type), sort this, and re-create the lists (see this excellent answer for an explanation of how).
First you should create a Data object to hold everything.
private class Data
{
public DateTime DateTime { get; set; }
public int Int32 { get; set; }
public string String { get; set; }
}
Then you can sort like this.
var l = new List<Data>();
l.Sort(
(a, b) =>
{
var r = a.DateTime.CompareTo(b);
if (r == 0)
{
r = a.Int32.CompareTo(b);
if (r == 0)
{
r = a.String.CompareTo(b);
}
}
return r;
}
);
I wrote a sort algorithm that does this for Nito.LINQ (not yet released). It uses a simple-minded QuickSort to sort the lists, and keeps any number of related lists in sync. Source code starts here, in the IList<T>.Sort extension method.
Alternatively, if copying the data isn't a huge concern, you could project it into a LINQ query using the Zip operator (requires .NET 4.0 or Rx), order it, and then pull each result out:
List<DateTime> one = ...;
List<double> two = ...;
List<string> three = ...;
var combined = one.Zip(two, (first, second) => new { first, second })
.Zip(three, (pair, third) => new { pair.first, pair.second, third });
var ordered = combined.OrderBy(x => x.first);
var orderedOne = ordered.Select(x => x.first);
var orderedTwo = ordered.Select(x => x.second);
var orderedThree = ordered.Select(x => x.third);
Naturally, the best solution is to not separate related data in the first place.
Using generic arrays, this can get a bit cumbersome.
One alternative is using the Array.Sort() method that takes an array of keys and an array of values to sort. It first sorts the key array into ascending order and makes sure the array of values is reorganized to match this sort order.
If you're willing to incur the cost of converting your List<T>s to arrays (and then back), you could take advantage of this method.
Alternatively, you could use LINQ to combine the values from multiple arrays into a single anonymous type using Zip(), sort the list of anonymous types using the key field, and then split that apart into separate arrays.
If you want to do this in-place, you would have to write a custom comparer and create a separate index array to maintain the new ordering of items.
I hope this could help :
one = one.Sort(delegate(DateTime d1, DateTime d2)
{
return Convert.ToDateTime(d2).CompareTo(Convert.ToDateTime(d1));
});

Doing Recursive Function using LINQ and Delegates

I was under the impression that the only difference between Func and Action is that the former has to have a return value.So I thought you can call a recursive linq from either a Func or Action. I am new to C# and I am just experimenting and curious.
So I tried the following to recursively print the nested types within a Type.
Type t = typeof(Lev1);
Action<Type> p1 = null, p2 = null;
p1 = tn =>
{
Console.WriteLine(tn.Name);
tn.GetNestedTypes().Select(x => { p1(x); return x; });
};
p2 = tn =>
{
Console.WriteLine(tn.Name);
tn.GetNestedTypes().ToList().ForEach(x => { p2(x);});
};
p1(t);
Console.WriteLine("=".PadRight(50, '='));
p2(t);
So the result I got was that p1 (which uses recursion from a Func-ie Select) only prints the top level whereas p2 which uses Action-ie Foreach prints all levels.
I thought Func is just a function def so recursion is valid. Sure my understanding is wrong can somebody explain
The reason you see only the top-level in the first implementation is because the Select is lazily evaluated. It only starts returning values when it needs to, for example when you iterate it (or when you call Sum or a number of other functions). If you add a ToList() call after the Select, it will work.
You must force the IEnumerable -- it is lazy! (It needn't always be, but be wary with LINQ methods!)
In this case, you discard the results (and the actions!). Oh, well!
You need to add .ToList() to the first Select() call because Linq functions are lazy. In the second call the recursion works because of List<>.ForEach() (which as the name stands does exactly what foreach statement does).

is it possible to use Take in LINQ when the query returns an anonymous type?

I'm trying to get the n-th element out of a list of anonymous types returned by a LINQ query where n is a random number from 0 to 100. Messed around with it a while now and I'm not getting anywhere. My code (with names changed to protect IP):
var query = from Table1 t1 in theContext.Table1
join Table2 t2 in theContext.Table2
on ...
where ...
select new
{
partNum = t1.part_number,
partSource = t2.part_source
}
int num = new Random().Next(0, 100);
// here's where the code I've tried fails
Can I somehow do a Take<T>(100).ToList<T>()[num] to get a single anonymous type with partNum and partSource? I ended up solving this by explicitly defining a type, but it seemed like I was missing a more elegant solution here. All I want to do is return a Dictionary<string, string> to the caller so I'd prefer not to have to define a type outside of this method.
Update: ElementAt doesn't work for this. I tried adding:
// get a random part from the parts list
int num = new Random().Next(0, query.Count() - 1 );
var nthElement = query.ElementAt(num);
And I got an exception: The query operator 'ElementAt' is not supported.
You should be able to use:
var item = query.Take(100).ToList()[num];
Of course, it would be more efficient to do:
var item = query.Skip(num).First();
I believe you just want the ElementAt extension method:
var nthElement = query.ElementAt(num);
No need to mess with Take queries or such, and certainly not ToList.

Categories

Resources