Channel Directions in C#

When using channels as function parameters, you can specify if a channel is meant to only send or receive values. This specificity increases the type-safety of the program. In C#, we can achieve similar behavior using BlockingCollection<T> with AddingCompleted property and TryAdd/TryTake methods.

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

class ChannelDirections
{
    // This Ping method only accepts a BlockingCollection for adding values.
    // It would be a runtime error to try to take from this collection.
    static void Ping(BlockingCollection<string> pings, string msg)
    {
        pings.TryAdd(msg);
    }

    // The Pong method accepts one BlockingCollection for taking (pings)
    // and a second for adding (pongs).
    static void Pong(BlockingCollection<string> pings, BlockingCollection<string> pongs)
    {
        string msg;
        if (pings.TryTake(out msg, TimeSpan.FromSeconds(1)))
        {
            pongs.TryAdd(msg);
        }
    }

    static void Main()
    {
        var pings = new BlockingCollection<string>(1);
        var pongs = new BlockingCollection<string>(1);

        Ping(pings, "passed message");
        Pong(pings, pongs);

        string result;
        if (pongs.TryTake(out result, TimeSpan.FromSeconds(1)))
        {
            Console.WriteLine(result);
        }

        pings.CompleteAdding();
        pongs.CompleteAdding();
    }
}

To run the program, compile and execute it:

$ csc ChannelDirections.cs
$ mono ChannelDirections.exe
passed message

In this C# version, we use BlockingCollection<T> to mimic the behavior of Go channels. The Ping method only adds to the collection, while the Pong method takes from one collection and adds to another. This approach provides a similar level of direction specificity as in the original Go code.

The Main method sets up the collections, calls the methods, and then prints the result. We use TryTake with a timeout to avoid indefinite blocking, and we call CompleteAdding on the collections when we’re done with them, which is similar to closing a channel in Go.

While this C# code provides similar functionality to the Go example, it’s worth noting that C# has other concurrency primitives that might be more idiomatic in certain scenarios, such as Task<T> and async/await.