I've been feeling guilty lately because, recently, I introduced a touch of Rust code into my employer's C# codebase. I don't mean that I literally added Rust to our build pipeline or included some kind of FFI call: rather, I mean that (without thinking about it) I added a few lines of code that, although written in C#, mimic a common Rust idiom.

var items = GetItems();
var otherItems = GetOtherItems();
var itemFilter = new HashSet<int>(items.Select(item => item.Key));

foreach (var item in otherItems)
{
    if (itemFilter.Add(item.Key))
    {
        items.Add(item);
    }
}
This is clearly a synthetic example; there were reasons, in the original, that it needed to be this way.

Though I normally wouldn't write this in either language, it's a fairly normal idiom in Rust: a set being used as part of a filter.

let mut set = HashSet::new();
let filtered_items = get_items_with_potential_duplicates()
    .filter(|x| items.insert(x.key));

The set appears on a line of its own, but of course it's consumed only by the closure I've passed to .filter(), and this has become normal for me in spite of the fact that it was, originally, a little bit strange. Thing is, it has become normal for me. It never used to be.

Linq includes a neat little extension method known as Distinct(). For primitive types, it does exactly what it says on the label: give distinct a sequence of numbers, and it will output each one only once. For actual classes and things, it's decidedly less useful, but there's an overload to solve that problem. Observe:

class ItemComparer : IEqualityComparer<Item>
{
    public bool Equals([AllowNull] Item x, [AllowNull] Item y)
    {
        if (x == null || y == null)
        {
            return false;
        }

        return x.Key == y.Key;
    }

    public int GetHashCode([DisallowNull] Item obj)
    {
        return obj.Key.GetHashCode();
    }
}
I have no idea what the Allow/Disallow stuff is about. OmniSharp did that. o.O

Now, if we go back to our original example and employ this fancy new class:

var items = GetItems();
var otherItems = GetOtherItems();
var uniqueItemsByKey = items.Concat(otherItems).Distinct(new ItemComparer());

This works fine. It's pretty. It's simple enough. There's not really anything wrong with it except that C# itself has some other idioms that just make this... Annoying. Like, for instance, where does that class go? I have to define it somewhere else, because you can't just go sticking classes like just wherever you want, all nimbly-pimbly, from tree to source tree. Besides which, this isn't really a part of C# idiom in any practical sense. I mean, how many times have you seen someone do this in the first place?

There's one thing about it that is idiomatic in C#, and that's indirection. The inability to directly observe what a given bit of code does is so common in C# that we barely give it a thought. Frankly, I'm not a fan of that bit of idiom. At the end of the day, I'm not totally sure what I think of this. Did I do the right thing or not?

I guess I'll leave that as an exercise to the reader.