I thought about creating a small library that would expose a static IComparer<T> ToComparer<T>(this Func<T, T, int>) extension method (and a similar one for IEqualityComparer<T>). But I so far just was too lazy. If somebody from the C# API team reads this tho.... :P
And while you're at it dear C# team, add .Distinct<TSource, TDest>(Func<TSource, TDest>, IEqualityComparer<TDest>) similar to OrderBy and friends, and various constructors to the generic collection types taking Func<T, T, int> instead of IComparer<T>. Yeah, API bloat, but good one in this case IMHO.
C# and the .NET runtime and the compiler(s) are still evolving indeed, and I would guess a lot of issues will be addressed and fixed in the future.
PS: the most annoying bit to me about the immutable collection library is that they are dead slow whenever I tried them.
1) Immutable collections is a bit of a pain in C# right now. No LINQ To() methods, some nugets don't actually work with them, constructor methods can be nicer...
His item 4 is to some extent a result of the limited immutable support (he mentions "a lightweight way of building immutable list/collection expression"). Fix that support and 1,4 out of his list are not much of a problem anymore or at least ameliorated.
2) I would be glad to never need hear of IComparer or IEqualityComparer again, which I only need due to the stdlib leftovers... Admittedly not much, but enough to be annoying. His solution is more general, but your idea is very nice. Why didn't I think of that?
5) Dynamic is there for the Dynamic Language Runtime and for dynamic code far more general than your example. It should be kept in that silo, but it does do its job there.
IMHO, the author tries to write C# with the current trend of functional+immutable, and that's why he ended up hitting these issues. It's true that the OOP way isn't bad (at least with those examples), but it's also true C# could offer better support for doing things the other way without much effort.
Tho, your own example of immutable collections is actually a good one. Rather specific, but still good. Immutable collections really could profit from compuiler-level warnings or even errors. It would be an easy enough fix for the language/compiler team: have some attribute that you/the immutable library authors would decorate functions with, something like [NoDiscardReturn] and the compiler would check that and emit warnings/errors.
(2) Implementing something like IComparer<> is something you rarely do, and more or less never the way the author does.
var enumerator = array.OrderBy(e => e.Foo).ThenBy(e => something.CheckProperty(e.Bar)).ThenByDescending(e => e.Name, StringComparer.OridinalIgnoreCase);
array = enumerator.ToArray();
// Tho you often do not need to call .ToArray() if you're only gonna traverse the enumerator once
Using Linq like this is more idiomatic and also kinda "functional", which appears to be what the author is after.
Now, this was specific to the comparer stuff, but in general a sane API these days would use Func<> or Action<> instead of single-method interfaces. There are still some leftovers in the C# stdlib (and probably more in third party libraries) but again, you can write a wrapper once - not the horrible mess the author proposed either, but an interface implementation that would use single Func<> parameter in the constructor and then call that in the method implementation.
internal class Foo
{
public string Bar;
public int Baz;
public override string ToString() => $"{Bar}: {Baz}";
}
internal class DontAnnoyMeComparer<T> : IComparer<T>
{
private readonly Func<T, T, int> cmpFunc;
internal DontAnnoyMeComparer(Func<T, T, int> cmpFunc)
{
this.cmpFunc = cmpFunc;
}
public int Compare(T x, T y) => cmpFunc(x, y);
}
static void Main(string[] args)
{
var foos = new[] {
new Foo { Bar = "World", Baz = 1 },
new Foo { Bar = "Hello", Baz = 2 },
new Foo { Bar = "Hello", Baz = 1 },
};
// First with some ideomatic Linq
Console.WriteLine(string.Join(", ", foos.OrderBy(f => f.Bar).ThenBy(f => f.Baz)));
// Then with our helper interface implementation
Array.Sort(foos, new DontAnnoyMeComparer<Foo>((x, y) => {
var res = x.Bar.CompareTo(y.Bar);
return res != 0 ? res : x.Baz.CompareTo(y.Baz);
}));
Console.WriteLine(string.Join(", ", (object[])foos));
}
// Output
// Hello: 1, Hello: 2, World: 1
// Hello: 1, Hello: 2, World: 1
(3) related directly to (2). The only real reason this is a real-world problem for the author is that they do not actually write idiomatic code.
(4) Sure, something like that would be nice sometimes. But the lack of it is hardly the end of the world. Tho on the flipside, in my opinion languages that offer such pseudo-DSLs or even real embdeddable DSLs quite often suffer from people writing DSLs for every freaking thing, thus reducing readability of their code.
(5) dynamic is hell for robust code, that's why you avoid it. I only ever used it when I was too lazy to do the extra typing to create proper types in run-once code dealing with JSON. And even then I should have probably just:
class MyType {
[DataMemeber]
public int Foo;
[DataMemeber(Name="baz")]
public int Baz;
}
I'm only asking if the snippet you posted is C# or something else, and noting that if it's C# I have learned something new about the language.
Except now that I look at it more specifically, it looks like a bound on the class's generic parameter rather than a condition on the interface implementation?
As in you're defining a SortedList class which implements ICollection and IEnumerable but requires T:IComparable, not defining a SortedList class which implements IEnumerable if T is IComparable.
And if that reading is correct, Java can do it just fine:
class SortedList<T extends Comparable<T>> implements Collection<T>, Iterable<T>
however it's got nothing to do with conditional conformance.
I only programmed .NET in the days of 2.0, so my problems with it come from an older less capable framework. Nonetheless, when your language of choice requires you to distinguish between:
- IComparable
- IComparable(Of T)
- IEquatable(Of T)
- IStructuralEquatable
- IStructuralComparable
- IEqualityComparer
- IEqualityComparer(Of T)
- IComparer(Of T)
- overriding Object.Equals
- overriding operators <,>, <=, >=, =, <>
...it's time to find a new language.
edit: Just imagine you encounter a new collection BozBag(Of T) which has to test your objects for equality. Without looking at documentation could you as a coder possibly know if BozBag uses x.Equals(y), x = y, x.CompareTo(y) = 0? Heck it could even use x.Equals(DirectCast(y,Object)) or x.CompareTo(DirectCast(y,Object)). The only way to really know is to dig through documentation.
This is another thing that I think .NET got (mostly) right, though the overall shape of things is complicated enough that it's still easy to get tripped up if you don't know all the details.
.NET has:
- Several basic ways doing comparisons: the == operator, the object.Equals() method, and the object.ReferenceEquals() method. == is intended to be for referential equality, and object.Equals() can optionally be overridden to implement value equality. == can be overridden (though it's not generally recommended), which is why there's the separate ReferenceEquals() method for when you absolutely must compare references.
- An IEqualityComparer interface for implementing custom equality. Collection classes that rely on determining if objects are equal (e.g., hash sets and dictionaries) let you supply one of these so that you can do purpose-specific equality.
- IComparable interface for when you want to implement a default ordering on a type.
- IComparer interface for when you want to use custom ordering rules.
I know of atleast one instance in C# where something like this happens. The IEnumberable<T>.Count() extension sees if the input is actually a ICollection and uses the count on that to get the count directly, rather than iterating over it.
I remember writing extension methods in C# for IEnumerable<T>, which would of course take in an IEnumerable<T>, but saw if the actual input was a ICollection<T>, IList<T> etc to optimize the operations.
I'd like to point out that the extension method feature of C# is one big reason why I like this whole story much better than the Streams API in Java. There are often methods I wish were part of the IEnumerable interface, and with extension methods, I can add them myself! (if they are not already provided in, say, MoreLinq [1]). Very convenient, and a big boost in productivity.
It's been a while since I last wrote C# code, does it really allow for heterogeneous value types? If so, that's cool. However, from my recollection, the C# API itself is all about opaque objects and behaviors. That's not a criticism of the paradigm, and I remember liking the C# standard libs and structures a lot better than Java's, but it's not something I would equate to generic heterogenous containers in dynamic languages. It's not in question whether you can replicate some of that in .NET, it's more about whether that's a core language idiom or not.
Edit: `collections.namedtuple` is Python, mixed that up.
Agree with everything here. I posted an example [1] of how this can be solved now with C# (although in reality it just adds another 'equality solution' to the mix). It uses ad-hoc polymorphism to implement a type-class (interface) and instance (struct) approach, similar to Haskell. There's no IEqualityComparer instance allocation cost either.
Which collection APIs were you missing? Between the standard interfaces and their default implementations[1] and the extension methods to IEnumerable[2] you can do quite a lot in my experience.
Yes, I picked this up, and was not making any statement against that. In fact I do not subscribe to the validity of his approach. But I gave a shot at seeing if I could give code that was succinct and matched.
The requirement was to add a method that works for all collections, whether platform or language specific, while preserving type. The code I gave is an approximation of a solution - to use a rough analogy: topologically speaking the code matches but loses the geometry. The code I gave works on basically all .NET collections, whether C# or F#, string or tree - as long as they implement the interface - they are matched. That it leverages the existing organization should not count against it. The failing is that although types are preserved it is under a new geometry or structure.
We try to use ICollection in our APIs instead of IEnumerable, since the latter can have surprising semantics like being a wrapper for some operation which may not be valid anymore, or might be slower than you expect to do things like .Count(). IMO it's really not best for transporting across interface boundries in the most common case; only when you're specifically trying to avoid having the whole collection in memory or something like that.
Another thing that can help is this wonderful May<T> library[1]. It a great option type[2] for .NET. It helps make operations more composable.
How do you do functional programming in C#? It has lambda functions, but it lacks sum types, pattern matching and immutable collections (they exist, but it's not much use if none of the libraries use them).
It seems that this structure could be implemented in C# with inheritance (to add new entities) and extension methods (to add new operations) and var keyword to achieve type inference to verify correctness at compile time.
LINQ + lambdas are great at this. Simple example: say you have a list of Car objects, and you want to get a new list comprising only the cars whose engines have greater than 300 horsepower, sort them in order of increasing price/horsepower ratio, and then take the top 20. In most languages, this requires a bunch of loops. In C#, it looks like this:
This results in a lazy-evaluated evaluated expression that works exactly the same whether cars is an in-memory list, a SQL table, a RavenDB collection, or anything else that implements the right interface (IEnumerable). It's not only concise, but also self-documenting. The syntax implies the semantics.
And while you're at it dear C# team, add .Distinct<TSource, TDest>(Func<TSource, TDest>, IEqualityComparer<TDest>) similar to OrderBy and friends, and various constructors to the generic collection types taking Func<T, T, int> instead of IComparer<T>. Yeah, API bloat, but good one in this case IMHO.
C# and the .NET runtime and the compiler(s) are still evolving indeed, and I would guess a lot of issues will be addressed and fixed in the future.
PS: the most annoying bit to me about the immutable collection library is that they are dead slow whenever I tried them.
reply