Code Review Videos > Map, Filter, Reduce > [Easy] Filter in C#

[Easy] Filter in C#

The following are the C# solutions to the filter problems defined under Map, Filter, Reduce.

As I am learning C# and much of this stuff is new to me, I will call out all the things I found interesting or different to what I am used to when working with TypeScript.

Easy Filter

// EasyFilterUnitTests.cs

using MapFilterReduceClassLibrary;
using NUnit.Framework;

namespace MapFilterReduceUnitTests;

public class EasyFilterUnitTests
{
    private static object[] FilterStringCases =
    {
        new object[]
        {
            new string[] { },
            new string[] { }
        },
        new object[]
        {
            new[] { "    " },
            new string[] { }
        },
        new object[]
        {
            new[] { "b" },
            new[] { "b" }
        },
        new object[]
        {
            new[] { "aaa", "bbb", "ccc", "a", "AAA", "Abba", "" },
            new[] { "bbb", "ccc" }
        }
    };

    [Test]
    [TestCase(new int[] { }, ExpectedResult = new int[] { })]
    [TestCase(new[] { 0 }, ExpectedResult = new[] { 0 })]
    [TestCase(new[] { 1 }, ExpectedResult = new int[] { })]
    [TestCase(new[] { 9, 8, 7, 6, 5, 4, 3, 2, 1 }, ExpectedResult = new[] { 8, 6, 4, 2 })]
    public int[] TestFilterNumbers(int[] input)
    {
        return EasyFilter.FilterNumbers(input);
    }

    [TestCaseSource(nameof(FilterStringCases))]
    public void TestFilterString(string[] input, string[] output)
    {
        Assert.AreEqual(
            EasyFilter.FilterString(input),
            output
        );
    }

    [Test]
    [TestCase(new bool[] { }, ExpectedResult = new bool[] { })]
    [TestCase(new[] { true }, ExpectedResult = new[] { true })]
    [TestCase(new[] { false }, ExpectedResult = new bool[] { })]
    [TestCase(new[] { true, true }, ExpectedResult = new[] { true, true })]
    [TestCase(new[] { false, false, false }, ExpectedResult = new bool[] { })]
    [TestCase(new[] { false, true, false, true, false, true }, ExpectedResult = new[] { true, true, true })]
    public bool[] TestFilterBool(bool[] input)
    {
        return EasyFilter.FilterBool(input);
    }
}Code language: PHP (php)

And the associated implementation:

// EasyFilter.cs

namespace MapFilterReduceClassLibrary;

public static class EasyFilter
{
    public static int[] FilterNumbers(int[] input)
    {
        return input.Where(i => i % 2 == 0).ToArray();
    }

    public static string[] FilterString(string[] input)
    {
        return input
            .Where(s => s.Trim().Length > 0)
            .Where(s => !s.Trim().ToLower().Contains("a"))
            .ToArray();
    }

    public static bool[] FilterBool(bool[] input)
    {
        return input.Where(b => b).ToArray();
    }
}Code language: PHP (php)

It’s pretty clear from the tests that something was different between this exercise and the previous exercise. We will cover that (TestFilterString) in due course.

C# uses Where not filter

I’m unsure whether idiomatic C# considers Where to be the correct approach to solving this kind of problem.

To the best of my knowledge, in C# the Where function is the closest equivalent to what I would use filter for in other languages.

When using Where we provide our own function that receives the current element being enumerated, and we must provide the logic to determine whether this element is kept (returns true) or should be filtered out (returns false).

Remembering Return Types

My initial stumbling block when working through this exercise was in using the correct return types for both the implementation and the test case.

Highlighted in red below, you can see I had used int, where I should have used int[].

This sounds obvious, but notice that because I had both set to int, the IDE didn’t flag anything wrong here. This would imply that it is not reading the TestCase attributes as the ExpectedResult should have given away my error.

A pretty easy fix though to start us off – just change up to int[] for both and away we go.

The Need For .ToArray()

A problem I’ve come across before with C# is that using LINQ methods – Where in this case – transforms the variable from one type (int[] here), into something else.

public static int[] FilterNumbers(int[] input)
{
    return input.Where(i => i % 2 == 0);
}Code language: C# (cs)

This isn’t valid:

I’m left with a question having read the error message:

Cannot convert expression type ‘System.Collections.Generic.IEnumerable<int>’ to return type ‘int[]’

Why is this typed as IEnumerable<int> and not IEnumerable<int[]> ?

I’m thinking this refers to the individual values being enumerated… I guess I just find this unintuitive, as I do with way most Microsoft code docs seem to be formatted.

What I gather is happening here is we provide an int[] array as input.

By using LINQ methods this results in our input being converted into some sub class of Enumerable. Or maybe it doesn’t sub class, but it always becomes something that implements IEnumerable.

At this point we need to explictly convert this IEnumerable representation back to a format that is useful to us. That is why we need to call .ToArray() on the result.

We can chain LINQ calls:

public static string[] FilterString(string[] input)
{
    return input
        .Where(s => s.Trim().Length > 0)
        .Where(s => !s.Trim().ToLower().Contains("a"))
        .ToArray();
}
Code language: C# (cs)

Or to put it another way, there is no need to call .ToArray() every time:

public static string[] FilterString(string[] input)
{
    return input
        .Where(s => s.Trim().Length > 0)
        .ToArray()
        .Where(s => !s.Trim().ToLower().Contains("a"))
        .ToArray();
}
Code language: JavaScript (javascript)

We provided an array as input, so we know this works. But to confirm it, arrays in C# do implement IEnumerable.

Prefer IEnumerable

It’s a subtle change, and one I only spotted whilst doing this write up.

In Rider there is a suggested refactoring here:

In other words, change:

// from this:
public static int[] FilterNumbers(int[] input)
public static string[] FilterString(string[] input)
public static bool[] FilterBool(bool[] input)

// to this:
public static int[] FilterNumbers(IEnumerable<int> input)
public static string[] FilterString(IEnumerable<string> input)
public static bool[] FilterBool(IEnumerable<bool> input)Code language: C# (cs)

In other words, code to an interface not to an implementation.

By making this change our three methods are not explicitly tied to receiving only their very specific types.

It’s not a necessary change here, but it is the sort of thing that brings flexibility to real world projects.

Cannot convert expression type ‘int‘ to return type ‘bool

A very common practice in JavaScript is to implicitly convert one type of thing to another. And that happens even if the developer isn’t aware they are doing it.

An example:

if (" a ".trim().length) {
  console.log("hello");
} else {
  console.log("goodbye");
}

// outputs:
console.log("hello");


if ("      ".trim().length) {
  console.log("hello");
} else {
  console.log("goodbye");
}

// outputs:
console.log("goodbye");Code language: JavaScript (javascript)

Why is that?

console.log(" a ".trim().length); // 1
console.log("".trim().length);     // 0Code language: C# (cs)

And through type coercion, the number zero is a falsy value.

But we can’t do that in C#, because C# is strongly typed and this approach fails at compile time.

return input.Where(s => s.Trim().Length).ToArray();

// error: Cannot convert expression type 'int' to return type 'bool'Code language: C# (cs)

As far as I can tell, the only true and false values in C# are the literal values true and false. For everything else, we need to do some kind of boolean check. In this case it is to check that the length of a trimmed string is greater than zero:

return input.Where(s => s.Trim().Length > 0).ToArray();Code language: C# (cs)

Makes sense. And in my opinion it makes for clearer code. This is a win for me.

NUnit Is No Fan Of string[]

Easily the most challenging part of this exercise was figuring out how to satisfy NUnit that the given TestCase parameters were arrays of string.

I saw this in the previous exercise when using string[] as input, and I needed to use new object[] {} instead:

[Test]
[TestCase(ExpectedResult = "")]
[TestCase(new object[] { "" }, ExpectedResult = "")]
[TestCase(new object[] { " " }, ExpectedResult = "")]
[TestCase(new object[] { "aaa" }, ExpectedResult = "aaa")]
[TestCase(new object[] { "aaa", "bbb", "ccc" }, ExpectedResult = "aaabbbccc")]
public string TestReduceString(params string[] input)
{
    return EasyReduce.ReduceString(input);
}Code language: C# (cs)

The difference between that code and this is that the ExpectedResult came out as a string.

In this instance the filter (or LINQ Where method) would result in an array of output.

Things went really screwy when I tried to do this:

It looks like it might work. But running these tests ends up with four errors:

They output in whatever order they run in, I don’t think I can easily control that when running them all. I did discover that I can run individual tests in isolation. That’s a nice improvement over Jest.

If we disregard the first and last tests, the second two give the same error:

[TestCase(new object[] { "    " }, ExpectedResult = new object[] { })]
[TestCase(new object[] { "b" }, ExpectedResult = new object[] { "b" })]

// giving

System.ArgumentException : Object of type 'System.String' cannot be converted to type 'System.String[]'.Code language: C# (cs)

I tried a bunch of things to figure this out, mostly around trying to use the Debugger, and Googling.

Neither yielded much success. I couldn’t get the breakpoint to hit, and the error seems fairly generic enough that Google didn’t provide much of use either.

In the end, intuition / gut feel said it was the use of ExpectedResult with the new object[] approach that was likely the root cause.

It was back over to the NUnit docs (which I have already praised, but will now do so again) and the very next attribute down from TestCase was TestCaseSource:

private static object[] _filterStringCases =
{
    new object[]
    {
        new string[] { },
        new string[] { }
    },
    new object[]
    {
        new[] { "    " },
        new string[] { }
    },
    new object[]
    {
        new[] { "b" },
        new[] { "b" }
    },
    new object[]
    {
        new[] { "aaa", "bbb", "ccc", "a", "AAA", "Abba", "" },
        new[] { "bbb", "ccc" }
    }
};Code language: C# (cs)

The syntax here doesn’t feel super nice to me. I reckon there’s a nicer approach to this of which I’m not yet aware.

I originally put the _filterStringCases code right above the TestFilterString test code, but as soon as I hit Save in the IDE it moved the declaration to the top of the file. Interesting.

_filterStringCases returns an array of objects.

Each element in the array is another array (new object[]) where I two entries. Feels like a tuple…

Then the first of the two elements is the input argument – the given.

And the second of the two array elements is the output – the ExpectedResult / expected.

[TestCaseSource(nameof(_filterStringCases))]
public void TestFilterString(string[] input, string[] output)
{
    Assert.AreEqual(
        EasyFilter.FilterString(input),
        output
    );
}Code language: C# (cs)

The nameof syntax is new to me. I need to read up on that. Not entirely sure what that is doing / how it works.

One other thing that caught me out was in my variable naming. Originally I went with FilterStringCases, but then the IDE shouted at me and strongly suggested I use _filterStringCases instead:

The rest of it feels fairly standard.

Refactoring TestFilterString To Use Tuples

But when writing this out, it dawned on me that above (feels like a tuple…) that likely it should be a tuple.. or could be, at the very least. Here is my attempt at a refactor to using an array of tuples:

private static object[] FilterStringCases =
{
    Tuple.Create(
        Array.Empty<string>(),
        Array.Empty<string>()
    ),
    Tuple.Create(
        new[] { "    " },
        Array.Empty<string>()
    ),
    Tuple.Create(
        new[] { "b" },
        new[] { "b" }
    ),
    Tuple.Create(
        new[] { "aaa", "bbb", "ccc", "a", "AAA", "Abba", "" },
        new[] { "bbb", "ccc" }
    )
};

[TestCaseSource(nameof(FilterStringCases))]
public void TestFilterString(Tuple<string[], string[]> input)
{
    Assert.AreEqual(
        EasyFilter.FilterString(input.Item1),
        input.Item2
    );
}
Code language: C# (cs)

In doing that exercise I learned:

  • Line 3: How to create a tuple (Tuple.Create)
  • Line 5: Prefer Array.Empty<string>() vs new string[] {}
  • Line 22: How to use a Tuple as an input argument to a function
  • Line 25: How to access tuple values

Pretty cool.

I wonder whether that’s some obscure conclusion I’ve come too, or whether that would be fairly standard to see in a real world C# project unit test. An unanswered question, for now. Please do leave a comment and share your opinions / experiences.

Struggling To Debug

Lastly for this exercise I did try to use the C# debugger.

Tried. But failed.

I managed to get by, but I almost fell back to my console.log roots here and reached for Console.WriteLine("whatever").

However, here’s the thing:

I have no idea where that whatever text is written too when running unit tests. I mean, I can put it right there in the code, run the tests… but where does it display?

Nowhere. At least nowhere I can see.

Fortunately I made it to the end of the exercise through sheer force of will and stubbornness to keep trying new things. Would have been nice to get some time spent with the debugger though.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.