Extension method not updating object passed in - c#

I recently started to learn about LINQ and came across the OrderBy extension method. This initially excited me because the syntax seemed much nicer than the Sort method that we use all over the place for Generic Lists.
For example, when sorting a list of languages, we normally do something like:
neutralCultures.Sort((x, y) => x.EnglishName.CompareTo(y.EnglishName));
This works well, but I prefer the syntax of OrderBy, which only requires that you pass in the property that you wish to sort by, like so:
neutralCultures.OrderBy(ci => ci.EnglishName);
The problem is that OrderBy returns IOrderedEnumerable, but I need List<T>. So, I set out to extend the Sort method for List<T> using the same signature that is used for OrderBy.
public static void Sort<TSource, TKey>(this List<TSource list, Func<TSource, TKey> keySelector)
{
list = list.OrderBy(keySelector).ToList<TSource>();
}
This would be called like:
neutralCultures.Sort(ci => ci.EnglishName);
Maybe I'm overlooking a simple implementation detail, but this code doesn't work. It compiles, but doesn't return an ordered list. I'm sure I can refactor this to make it work, but I was just wondering why setting list within the extension method doesn't work.

I've written Sort variants using a selector before - it isn't hard... something like:
using System;
using System.Collections.Generic;
class Foo
{
public string Bar { get; set; }
}
static class Program
{
static void Main()
{
var data = new List<Foo> {
new Foo {Bar = "def"},
new Foo {Bar = "ghi"},
new Foo {Bar = "abc"},
new Foo {Bar = "jkl"}
};
data.Sort(x => x.Bar);
foreach (var item in data)
{
Console.WriteLine(item.Bar);
}
}
static void Sort<TSource, TValue>(
this List<TSource> source,
Func<TSource, TValue> selector)
{
var comparer = Comparer<TValue>.Default;
source.Sort((x,y) => comparer.Compare(selector(x), selector(y)));
}
}

That's the behaviour I would expect. Imagine if such a thing were possible:
var c = myCustomer;
myCustomer.DoStuff();
if (c == myCustomer) // returns false!!!
Simply calling a method on an object (which is what an extension method looks like to the user of your framework) shouldn't change which instance the reference points to.
For your example, I would stick with Sort.

I think it is because list is not passed by ref. Thus setting the list variable to another object doesn't alter where the original variable was pointing.
You might be able to do this (haven't tested it properly):
public static void Sort<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
{
var tempList = list.OrderBy(keySelector).ToList<TSource>();
list.Clear();
list.AddRange(tempList);
}

You are replacing the value of the local parameter variable called list, not changing the value of the caller's variable (which is what you are trying to do.)

Related

IEnumerable<IEnumerable>> and Extension Method

I was wondering if someone could help me understand the following behavior. In the following code, I am creating a CustomObject instance, which contains a single Property of type IEnumerable<IEnunumerable>>.
I also have an extension Method on IEnumerable<T> called AsDataTable.
public class CustomObject
{
public IEnumerable<IEnumerable> Collection {get;set;}
}
public static class ExtensionMethods
{
public static bool AsDataTable<T>(this IEnumerable<T> list)
{
Console.Write("In extension method");
return default(bool);
}
}
void Main()
{
var ttObject = new CustomObject()
{
Collection = new List<IEnumerable>
{
new List<int>{1,2,3},
new [] {new{A="abc",B="def"}}
}
};
var dummy = new []{new {a='r'}}.AsDataTable();
foreach(var item in ttObject.Collection)
{
var temp = item.AsDataTable();
Console.WriteLine($"Item is IEnumerable : {item is IEnumerable}");
}
}
What makes me wonder if the following line of code works (or rather compiles)
var dummy = new []{new {a='r',b='3'}}.AsDataTable();
while when I loop over the Collection Property of CustomObject and then do the same it doesn't allow me to compile.
var temp = item.AsDataTable(); // this doesn't work
Curiously the following line returns true reconfirming 'item' is indeed IEnumerable.
Console.WriteLine($"Item is IEnumerable : {item is IEnumerable}");
I guess it is because the extension method is written over Generic version IEnumerable<T>, but then how is it that it works over the anonymous type array (outside the CustomObject).
IEnumerable<T> implements IEnumerable, not vice versa.
Through a bit of runtime hacking, SomeType[] actually does implement IEnumerable<SomeType>. On the other hand, IEnumerable doesn't - and overload resolution is done at compile time, so the compiler has no idea that your IEnumerable items in the collection actually also implement IEnumerable<int>.
If you need to work with IEnumerable, you need to use that in your extension method.

Find dictionary matches from list of base class

I have a quite odd question. I am working on a big project and i am implementing a core feature after millions of lines of code. I am not allowed to alter some parts of the code and it seems like i have to try this way.
I have two classes as A and B. A is derived from B.
public class B{}
public class A : B{}
I have a SortedList<string, A>, i need to cast the SortedList into List<B>. Operate on List<B> then cast the list into SortedList again.
For example:
SortedList<string, B> items;
public List<A> GetItems()
{
return items.Values.Cast<B>().ToList(); //Getting values works
}
public void SetItems(List<B> newItems)
{
items = CastWithMagic(newItems); //How to make casting work?
}
There are 2 possible changes on A items.
Changes on B(base class variables)
Removed items from the list.
I want to apply changes on List<B> to SortedList<string, A>
There are 2 possible changes on A items.
Changes on B(base class variables)
Removed items from the list.
Changes to the objects will be reflected in both lists, since the lists just contain references to the same objects. So all you should need to handle is objects that are deleted from the List. I believe you'll have to loop through the sorted list to see if the objects have been removed from the list:
public void SetItems(List<B> newItems)
{
foreach(string key in items.Keys)
{
if(!newItems.Contains(items[key] as B))
items.Remove(key);
}
}
Note that this is inefficient since you're looping through both collections, making it O(m*n) - if your collections are small and performance is not critical then you may be OK, but start with this and find ways to make it more efficient (maybe removing from the source collection instead of the copied list?)
You might be able to use Linq's 'select' function for this. Take this example code:
private void test()
{
List<alphaClass> firstList = new List<alphaClass>();
List<betaClass> secondList = firstList.Select(z => (betaClass)z).ToList();
}
public class alphaClass { }
public class betaClass : alphaClass { }
(This assumes everything from the list can be cast as the derived class.)
Anyway, linq's SELECT statement can be used to transform an IEnumerable into a different form. In this case, transforming it from one class to another.
EDIT: Whoops - missed the Sorted List part. That can be taken care of by using an extension method I found for a different question:
public static class ExtensionMethod
{
public static SortedList<TKey, TValue> ToSortedList<TSource, TKey, TValue>
(this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector,
Func<TSource, TValue> valueSelector)
{
var ret = new SortedList<TKey, TValue>();
foreach (var element in source)
{
ret.Add(keySelector(element), valueSelector(element));
}
return ret;
}
}
... and then, from here, you can use that extension like this:
SortedList<string, betaClass> myList = new SortedList<string, betaClass>();
SortedList<string, alphaClass> secondList;
secondList = myList.ToSortedList(kvp => kvp.Key, kvp => (alphaClass) kvp.Value);

way to go from list of objects to SortedList<string,string> with two of the fields using LINQ

I have class with 5 fields.
public class Test
{
public string name;
public string description;
public int int1;
public int int2;
public int int3;
}
In one of my function I have List<Test> list which has 10 items. Here I want SortedList<string,string> for two properties name & description.
I know, I can achieve this using for each but I want to know How can I do this using LINQ?
Use this:
var result = list.OrderBy(x => x.Name).ThenBy(x => x.Description);
Important:
Don't use multiple calls to OrderBy as they overwrite each other.
The sorted result will be in result. The original list remains unchanged.
The answer from #HugoRune is quite exhaustive, but because you said you want to use Linq, I'd suggest to add an extension method in your scope to help you with your goal:
static class SortedListExtensions
{
public static SortedList<K, V> ToSortedList<K, V, T>(
this IEnumerable<T> source,
Func<T, K> keySelector, Func<T, V> valueSelector)
{
return new SortedList<K,V>(
source.ToDictionary(
cur => keySelector(cur),
cur => valueSelector(cur)));
}
}
this way your SortedList creation is composable in Linq computations:
var sl = list.ToSortedList(f => f.name, f => f.description);
A C# SortedList is a type of dictionary, not actually a list.
If you indeed want a SortedList containing the names as keys and the descriptions as values, you can use this:
SortedList slist = new SortedList(list.ToDictionary(t=>t.name, t=>t.description))
Be aware that if a name occurs twice this will throw an exception, since dictionary keys have to be unique.
For most practical purposes however, the solution posted by Daniel Hilgarth is what I would use, unless you have a library function that specifically requires a SortedList as parameter.

Appending an element to a collection using LINQ

I am trying to process some list with a functional approach in C#.
The idea is that I have a collection of Tuple<T,double> and I want to change the Item 2 of some element T.
The functional way to do so, as data is immutable, is to take the list, filter for all elements where the element is different from the one to change, and the append a new tuple with the new values.
My problem is that I do not know how to append the element at the end. I would like to do:
public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
return collection.Where(x=>!x.Item1.Equals(term)).Append(Tuple.Create(term,value));
}
But there is no Append method. Is there something else?
I believe you are looking for the Concat operator.
It joins two IEnumerable<T> together, so you can create one with a single item to join.
public List<Tuple<T,double>> Replace(List<Tuple<T,double>> collection, T term,double value)
{
var newItem = new List<Tuple<T,double>>();
newItem.Add(new Tuple<T,double>(term,value));
return collection.Where(x=>!x.Item1.Equals(term)).Concat(newItem).ToList();
}
It seems that .NET 4.7.1 adds Append LINQ operator, which is exactly what you want. Unlike Concat it takes a single value.
By the way, if you declare a generic method you should include type parameter(s) after its name:
public List<Tuple<T, double>> Replace<T>(List<Tuple<T, double>> collection, T term, double value)
{
return collection.Where(x => !x.Item1.Equals(term))
.Append(Tuple.Create(term, value))
.ToList();
}
LINQ is not for mutation.
Functional programming avoid mutation.
Thus:
public IEnumerable<Tuple<T,double>> Extend(IEnumerable<Tuple<T,double>> collection,
T term,double value)
{
foreach (var x in collection.Where(x=>!x.Item1.Equals(term)))
{
yield return x;
}
yield return Tuple.Create(term,value);
}
If you're willing to use an additional package, check out MoreLinq, available on Nuget. This provides a new overload to the Concat-Function:
public static IEnumerable<T> Concat<T>(this IEnumerable<T> head, T tail);
This function does exactly what was asked for, e.g. you could do
var myEnumerable = Enumerable.Range(10, 3); // Enumerable of values 10, 11, 12
var newEnumerable = myEnumerable.Concat(3); // Enumerable of values 10, 11, 12, 3
And, if you like LINQ, you will probably like a lot of the other new functions, too!
Additionally, as pointed out in a discussion on the MoreLinq Github-page, the function
public static IEnumerable<TSource> Append<TSource>(this IEnumerable<TSource> source, TSource element);
with a different name but the same functionality is available in .NET Core, so it might be possible that we will see it for C# in the future.
This should do what you want (although it uses mutation inside, it feels functional from a callers perspective):
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
var result = collection.Where(x => !x.Item1.Equals(term)).ToList();
result.Add(Tuple.Create(term, value));
return result;
}
A alternative way to do it is to use "map" (select in LINQ):
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
return collection.Select(x =>
Tuple.Create(
x.Item1,
x.Item1.Equals(term) ? value : x.Item2)).ToList();
}
But it might give you different results than your original intention. Although, to me, that's what I think when I see a method called Replace, which is, replace-in-place.
UPDATE
You can also create what you want like this:
public List<Tuple<T, double>> Replace(List<Tuple<T, double>> collection, T term, double value) {
return collection.
Where(x => !x.Item1.Equals(term)).
Append(Tuple.Create(term, value)).
ToList();
}
Using Concat, as mentioned by Oded:
public static class EnumerableEx {
public static IEnumerable<T> Append<T>(this IEnumerable<T> source, T item) {
return source.Concat(new T[] { item });
}
}
One way is to use .Concat(), but you need to have a enumerable rather than a single item as the second argument. To create an array with a single element does work, but is combersome to write.
It is better to write an custom extension method to do so.
One method is to create a new List<T> and add the items from the first list and then the items from the second list. However, it is better to use the yield-keyword instead, so you do not need to create an list and the enumerable will be evaluated in a lazy fashion:
public static class EnumerableExtensions
{
public static IEnumerable<T> Concat<T>(this IEnumerable<T> list, T item)
{
foreach (var element in list)
{
yield return element;
}
yield return item;
}
}
The closest answer I could find came from this post and is:
return collection.Where(x=>!x.Item1.Equals(term)).Concat(new[]{Tuple.Create(term,value)});

How to specify a list selection method?

I've got a method that computes a list. At certain points in the algorithm a single element from the list needs to be chosen. It doesn't really matter which element is chosen, but I'd like to leave it up to the user to decide.
Right now, I've added an extension method IList<T>.Random() which simply takes a random element. .First() would have worked equally as well. Supposing I want to let the user pick which method is used, or perhaps an entirely different method, how would that look?
I was thinking about using an enum with limited options, and then I could wrap each of these calls in a switch and call the appropriate function. But maybe some sort of lambda function would be more appropriate?
This method needs to be used in two different places, once on a List<char> and once on a List<string>. I want to use the same method for both.
This isn't a GUI app. I'm trying to decide how to design the API.
Specifically, I want to have a field like
public Func<IList<T>, T> SelectElement = list => list.First();
Which would then be used in the method,
public string Reverse(string pattern, IList<object> args = null, IDictionary<string, object> kwargs = null)
But generic fields aren't possible. So I'm looking for an alternative solution. One would be to make the SelectElement method an argument to Reverse(), then I could make it generic... but I was hoping to keep it at a class-level for re-usability. Don't want to pass any more args to the function if I can help it.
Edit: full source code
how about this:
public class MyClass
{
public static class C<T>
{
public static Func<IList<T>, T> SelectElement;
}
public int Test(IList<int> list)
{
return C<int>.SelectElement(list);
}
}
static class Program
{
static void Main(string[] args)
{
MyClass.C<char>.SelectElement = xs => xs.First();
MyClass.C<int>.SelectElement = xs => xs.First();
var list = new List<int>(new int[] { 1, 2, 3 });
var c = new MyClass();
var v = c.Test(list);
Console.WriteLine(v);
}
}
Here's an extremely basic example I put together using a generic method that takes in a Func<IEnumerable<T>, T> for selecting an item from the list and then returns the result. I've done a few examples of how to call it:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Test
{
class Program
{
static void Main(string[] args)
{
//Simple list.
var list = new List<int> { 1, 2, 3, 4 };
// Try it with first
var result = DoItemSelect(list, Enumerable.First);
Console.WriteLine(result);
// Try it with last
result = DoItemSelect(list, Enumerable.Last);
Console.WriteLine(result);
// Try it with ElementAt for the second item (index 1) in the list.
result = DoItemSelect(list, enumerable => enumerable.ElementAt(1));
Console.WriteLine(result);
}
public static T DoItemSelect<T>(IEnumerable<T> enumerable, Func<IEnumerable<T>, T> selector)
{
// You can do whatever you method does here, selector is the user specified func for
// how to select from the enumerable. Here I just return the result of selector directly.
return selector(enumerable);
}
}
}
If you want to limit the choices a user has you could follow the route of an enum and make this method a private method and then have a way to convert the enum to the appropriate selector delegate to pass to the underlying private method.
public Func<IList<object>, object> SelectElement = list => list.First();
private T _S<T>(IEnumerable<T> list)
{
return (T)SelectElement(list.Cast<object>().ToList());
}
I can make the anonymous method work on objects, thereby avoiding generics, and then add a helper method which is what I'll actually use to call it. A little ugly, but seems to work.
This works for chars and strings. Haven't tested with other types. Built this before I saw Ralph's code, which is practically the same.
LINQPad code:
void Main()
{
var chars = new List<char>();
var strings = new List<string>();
chars.AddRange(new char[] {'1','2','4','7','8','3'});
strings.AddRange(new string[] {"01","02","09","12","28","52"});
chars.Dump();
strings.Dump();
Func<IList<object>, string> SelectFirst = ( list )
=> list.First().ToString();
Func<IList<object>, string> SelectLast = ( list )
=> list.Last().ToString();
Func<IList<object>, string> SelectRandom = ( list )
=> list.ElementAt( new Random().Next(0, list.Count())).ToString();
SelectBy(SelectFirst, strings.Cast<object>().ToList()).Dump();
SelectBy(SelectFirst, chars.Cast<object>().ToList()).Dump();
SelectBy(SelectLast, strings.Cast<object>().ToList()).Dump();
SelectBy(SelectLast, chars.Cast<object>().ToList()).Dump();
SelectBy(SelectRandom, strings.Cast<object>().ToList()).Dump();
SelectBy(SelectRandom, chars.Cast<object>().ToList()).Dump();
}
private string SelectBy(Func<IList<object>, string> func, IList<object> list)
{
return func(list);
}

Categories

Resources