Try to make something like C ++ cin with C #

5 minute read

Caution

This article is a detailed explanation added to the article C # and C ++ cin-like that I wrote on the old blog. It is a modified version.

TL; DR

–I tried to reproduce the usability class similar to C ++ cin in C #.
–However, the inputs are read together first.
–Finally, you will be able to read the standard input as shown below.

input

5
John
100 foo 200

code

static void Main(string[] args)
{
    //Read from standard input
    int N = cin;
    string name = cin;
    (int A, string str, int B) = cin;

    //Export to standard output
    Console.WriteLine(N);
    Console.WriteLine(name);
    Console.WriteLine($"{A} {str} {B}");
}

Output (just like input)

5
John
100 foo 200

However

――I wrote it for the purpose of a thought experiment / example, “You can do this with C #!” Rather than for practical purposes.
――The general code is doing bad things. (Type conversion operator has side effects)
–It does not completely imitate the syntax around cin.
–It may depend on behavior that is not guaranteed by the specification. (I have not checked whether the evaluation order around decomposition substitution is guaranteed)

About cin in C ++

In C ++, you can easily read text data separated by spaces and line breaks using cin.

I illustrated it earlier

5
John
100 foo 200

The data is

int N, A, B;
string name, str;

//Read from standard input
cin >> N;
cin >> name;
cin >> A >> str >> B;

//Export to standard output
cout << N << endl;
cout << name << endl;
cout << A << " " << str << " " << B << endl;

It can be read with the C ++ code.
I will omit the explanation of cout used for writing to standard output, and explain the code around cin.
cin >> N; reads a number from standard input, and cin >> name; reads a string from standard input. You can also read multiple values in one line of code, such as cin >> A >> str >> B. The data to be read is properly tokenized according to blanks and line breaks before being interpreted. From this point of view, the following essence seems to be the reason why the above usage of cin is easy to use.

–The input is properly tokenized.
–You can retrieve the value without writing a method call.
–The value can be retrieved as a numerical value or a character string depending on the type of the variable.
–You can retrieve values for multiple variables in one line.

Implemented in C

Create a Cin class that realizes the above essence.

Function to divide into tokens and read

We will read the standard input all at once when the class is initialized, and save it in the tokens private field by separating it into tokens. Since the tokens field is used to retrieve values one by one from the beginning, Queue.


class Cin {
    private Queue<string> tokens;

    public Cin() {
        string line;
        tokens = new Queue<string> ();
        while ((line = Console.ReadLine ()) != null) {
            foreach (var token in line.Split (' ')) {
                tokens.Enqueue (token);
            }
        }
    }
}

Read into a variable as an integer type

Aim for a writing taste like ʻint N = cin; `.
[Implicit type conversion] to retrieve int type value from Cin class instance cin (https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/operators/user-defined- conversion-operators) Utilize (abuse?).

public static implicit operator int(Cin cin) => int.Parse(cin.tokens.Dequeue());

It defines a type converter that takes one token from the queue and returns the result of parsing it to an int. This type conversion has side effects. (The Dequeue method returns the first value in the queue and removes it from the queue.) It’s badly behaved, but I don’t care this time because it’s a precious sacrifice of cin-like writing. I will do it.

Up to this point, the following code works.

int a = cin;
int b = cin;
int c = cin;

You can now read an integer value for each token. The writer does not need to explicitly call the conversion method.

Read into a variable as a string type

Aim for a writing taste like string str = cin;.
The implementation is almost the same as reading an integer type. It’s simpler because it doesn’t require parsing to integers.

public static implicit operator string(Cin cin) => cin.tokens.Dequeue();

Up to this point, the following code will work.

int a = cin;
string str1 = cin;
int b = cin;

The value can be retrieved with the same = cin without distinguishing between integer type and string type.

Retrieving values into multiple variables in one line

Aim for writing taste like (int A, string str, int B) = cin;.
csharp has a function to enable split assignment by user-defined class. This time we will use this.

To enable (int a, string b) = cin, you can define the following deconstruct method.

public void Deconstruct(out Cin o1, out Cin o2) => (o1, o2) = (this, this);

It’s a little complicated, so I’ll explain it step by step. First, this is assigned to ʻo1, ʻo2, so (int a, string b) = cin is the same as (int a, string b) = (cin, cin). Become. The left and right cins are converted to int and string by the implicit type converter defined earlier, respectively.
Define a similar deconstruct method up to the number of tuple elements that are likely to be practical. Below, we have defined up to a tuple with 8 elements.

public void Deconstruct(out Cin o1, out Cin o2) =>
    (o1, o2) = (this, this);
public void Deconstruct(out Cin o1, out Cin o2, out Cin o3) =>
    (o1, o2, o3) = (this, this, this);
public void Deconstruct(out Cin o1, out Cin o2, out Cin o3, out Cin o4) =>
    (o1, o2, o3, o4) = (this, this, this, this);
public void Deconstruct(out Cin o1, out Cin o2, out Cin o3, out Cin o4, out Cin o5) =>
    (o1, o2, o3, o4, o5) = (this, this, this, this, this);
public void Deconstruct(out Cin o1, out Cin o2, out Cin o3, out Cin o4, out Cin o5, out Cin o6) =>
    (o1, o2, o3, o4, o5, o6) = (this, this, this, this, this, this);
public void Deconstruct(out Cin o1, out Cin o2, out Cin o3, out Cin o4, out Cin o5, out Cin o6, out Cin o7) =>
    (o1, o2, o3, o4, o5, o6, o7) = (this, this, this, this, this, this, this);
public void Deconstruct(out Cin o1, out Cin o2, out Cin o3, out Cin o4, out Cin o5, out Cin o6, out Cin o7, out Cin o8) =>
    (o1, o2, o3, o4, o5, o6, o7, o8) = (this, this, this, this, this, this, this, this);

The following code works up to this point.

(int a, string b, int c) = cin;

You can now retrieve values for multiple variables in one line.

Summary

I was able to create a class in C # that is similar to C ++’s cin.
The code this time is here.