C
C#4mo ago
Jason_Bjorn

✅ Custom `IComparer` adds so much overhead

Is there a way I can avoid having to make a whole new class for the IComparer interface here? https://dotnetfiddle.net/yRh9YN
using System.Collections.Generic;
using System;
#nullable enable

SortedList<string, int> l = new(new RomanNumeralComparer())
{
// shuffled on purpose
{ "V", 5 },
{ "I", 1 },
{ "L", 50 },
{ "X", 10 },
};


// correctly prints in the order of
// I V X L
foreach (var c in l)
{
Console.WriteLine(c);
}

// big class for something so small
public class RomanNumeralComparer : IComparer<string>
{
private readonly string _order = "IVXL";

public int Compare(string? x, string? y)
{
if (x is null && y is null)
{
return 0;
}

if (x is null)
{
return -1;
}

if (y is null)
{
return 1;
}

int xRank = _order.IndexOf(x, StringComparison.InvariantCulture);
int yRank = _order.IndexOf(y, StringComparison.InvariantCulture);

return (xRank < yRank) ? -1 : (xRank == yRank) ? 0 : 1;
}
}
using System.Collections.Generic;
using System;
#nullable enable

SortedList<string, int> l = new(new RomanNumeralComparer())
{
// shuffled on purpose
{ "V", 5 },
{ "I", 1 },
{ "L", 50 },
{ "X", 10 },
};


// correctly prints in the order of
// I V X L
foreach (var c in l)
{
Console.WriteLine(c);
}

// big class for something so small
public class RomanNumeralComparer : IComparer<string>
{
private readonly string _order = "IVXL";

public int Compare(string? x, string? y)
{
if (x is null && y is null)
{
return 0;
}

if (x is null)
{
return -1;
}

if (y is null)
{
return 1;
}

int xRank = _order.IndexOf(x, StringComparison.InvariantCulture);
int yRank = _order.IndexOf(y, StringComparison.InvariantCulture);

return (xRank < yRank) ? -1 : (xRank == yRank) ? 0 : 1;
}
}
C# Online Compiler | .NET Fiddle
Test your C# code online with .NET Fiddle code editor.
16 Replies
Jason_Bjorn
Jason_BjornOP4mo ago
I have a SortedList of roman numerals where I want lower value numeral to appear earlier in the sorted order. I do not want to insert based on their numeric value, but rather via a custom IComparer interface. just for the fun of it
reflectronic
reflectronic4mo ago
i don't see where the overhead is, there is a big method body which is more or less irreducible and there are 4 lines of extra code for the class you can use Comparer.Create to save a couple of those lines i guess
Jason_Bjorn
Jason_BjornOP4mo ago
It's a little neater @reflectronic
SortedList<string, int> l = new(Comparer<string>.Create(CompareForRomanNumerals))
{
{ "V", 5 },
{ "I", 1 },
{ "L", 50 },
{ "X", 10 },
};


foreach (var c in l)
{
Console.WriteLine(c);
}

int CompareForRomanNumerals(string? x, string? y)
{
string order = "IVXL";

if (x is null && y is null)
{
return 0;
}

if (x is null)
{
return -1;
}

if (y is null)
{
return 1;
}

int xRank = order.IndexOf(x, StringComparison.InvariantCulture);
int yRank = order.IndexOf(y, StringComparison.InvariantCulture);

return (xRank < yRank) ? -1 : (xRank == yRank) ? 0 : 1;
}
SortedList<string, int> l = new(Comparer<string>.Create(CompareForRomanNumerals))
{
{ "V", 5 },
{ "I", 1 },
{ "L", 50 },
{ "X", 10 },
};


foreach (var c in l)
{
Console.WriteLine(c);
}

int CompareForRomanNumerals(string? x, string? y)
{
string order = "IVXL";

if (x is null && y is null)
{
return 0;
}

if (x is null)
{
return -1;
}

if (y is null)
{
return 1;
}

int xRank = order.IndexOf(x, StringComparison.InvariantCulture);
int yRank = order.IndexOf(y, StringComparison.InvariantCulture);

return (xRank < yRank) ? -1 : (xRank == yRank) ? 0 : 1;
}
But I think it gets put into a class anyway I wish the IL viewer in rider wasn't so dumb, I am trying to see if the compiler is putting it into a hidden class
Jason_Bjorn
Jason_BjornOP4mo ago
I guess it does not create a hidden class.
No description
Jason_Bjorn
Jason_BjornOP4mo ago
I thought everything was in a class in c#
reflectronic
reflectronic4mo ago
a method is not a class
Jason_Bjorn
Jason_BjornOP4mo ago
I know, but I thought it would put it in a hidden class. Or maybe I am thinking of java
reflectronic
reflectronic4mo ago
yes, maybe in java this is how functional interfaces are implemented it is not how delegates are implemented in C# a delegate object's constructor accepts the method directly
Jason_Bjorn
Jason_BjornOP4mo ago
thank you @reflectronic Now I gotta try to use a switch pattern instead of these old school if statements
SortedList<string, int> l = new(Comparer<string>.Create(CompareForRomanNumerals))
{
{ "V", 5 },
{ "I", 1 },
{ "L", 50 },
{ "X", 10 },
};


foreach (var c in l)
{
Console.WriteLine(c);
}

int CompareForRomanNumerals(string? x, string? y)
{
string order = "IVXL";

return (x, y) switch
{
(null, null) => 0,
(null, _) => -1,
(_, null) => 1,
_ => (order.IndexOf(x, StringComparison.InvariantCulture),
order.IndexOf(y, StringComparison.InvariantCulture)) switch
{
var (xRank, yRank) when xRank < yRank => -1,
var (xRank, yRank) when xRank > yRank => 1,
_ => 0
}
};
}
SortedList<string, int> l = new(Comparer<string>.Create(CompareForRomanNumerals))
{
{ "V", 5 },
{ "I", 1 },
{ "L", 50 },
{ "X", 10 },
};


foreach (var c in l)
{
Console.WriteLine(c);
}

int CompareForRomanNumerals(string? x, string? y)
{
string order = "IVXL";

return (x, y) switch
{
(null, null) => 0,
(null, _) => -1,
(_, null) => 1,
_ => (order.IndexOf(x, StringComparison.InvariantCulture),
order.IndexOf(y, StringComparison.InvariantCulture)) switch
{
var (xRank, yRank) when xRank < yRank => -1,
var (xRank, yRank) when xRank > yRank => 1,
_ => 0
}
};
}
Is there a way I can avoid having two var (xRank, yRank)s here? Can I someone name the expession preceeding the 2nd switch and use those names in the cases that follow?
Jason_Bjorn
Jason_BjornOP4mo ago
this doesn't work
No description
Jason_Bjorn
Jason_BjornOP4mo ago
I know this is a crazy scenario but I want to avoid accidently flipping the names (yRank, xRank)
reflectronic
reflectronic4mo ago
i would just do _ => order.IndexOf(x, StringComparison.InvariantCulture) - order.IndexOf(y, StringComparison.InvariantCulture)
Jason_Bjorn
Jason_BjornOP4mo ago
That's clever and I thought about doing that but I don't know if it is readable at a glance. What do you think @reflectronic I know you said that that is what you would do. How readable do you think it is?
canton7
canton74mo ago
It's one of those "tricks". Once you've seen it, you recognise it in future Just be careful with it in general. It won't happen with IndexOf, but you can end up overflowing if you're dealing with large ints
SleepWellPupper
SleepWellPupper4mo ago
or CompareTo the indices, since int is IComparable
Jason_Bjorn
Jason_BjornOP4mo ago
thank you. I will use it that would work too, thanks
return (x, y) switch
{
(null, null) => 0,
(null, _) => -1,
(_, null) => 1,
_ => order.IndexOf(x, StringComparison.InvariantCulture).CompareTo(
order.IndexOf(y, StringComparison.InvariantCulture)) switch
{
< 0 => -1,
> 0 => 1,
_ => 0
}
};
return (x, y) switch
{
(null, null) => 0,
(null, _) => -1,
(_, null) => 1,
_ => order.IndexOf(x, StringComparison.InvariantCulture).CompareTo(
order.IndexOf(y, StringComparison.InvariantCulture)) switch
{
< 0 => -1,
> 0 => 1,
_ => 0
}
};
Is there any overhead here that warrants switching from CompareTo to just subtracting the two values?
Want results from more Discord servers?
Add your server