20201020のC#に関する記事は2件です。

[C#]演算子のオーバーロードとプリミティブ型

はじめに

演算子のオーバーロードはop_xxxというメソッドが内部で定義されるようですが、int型などはこのメソッドを持っていません。
ILの仕様と言われればそれまでですが、ILの仕様を熟知していないので、とりあえず実験してみました。
Type.IsPrimitiveと何か関係があるのかなと思い、組み合わせて検証しました。

いい感じの検索ワードが思いつかず検証してみたので、(なんの役に立つのかはわかりませんが、)自分用のメモとして残します。

環境

Windows 10
Visual Studio 2019

参考

プリミティブ型について
https://ufcpp.net/blog/2016/12/tipsprimitives/

検証1

OP.cs
foreach (var t in types) {
    bool haveOverloadOperator = t.GetMethods().Count(x => x.Name.StartsWith("op_")) > 0;
    Console.WriteLine($"{t.Name} : {t.IsPrimitive} : {haveOverloadOperator}");
}
Boolean : True : False
Byte : True : False
SByte : True : False
Int16 : True : False
UInt16 : True : False
Int32 : True : False
UInt32 : True : False
Int64 : True : False
UInt64 : True : False
IntPtr : True : True
UIntPtr : True : True
Char : True : False
Double : True : True
Single : True : True
Decimal : False : True
Object : False : False
String : False : True
DateTime : False : True
TimeSpan : False : True

op_から始まるメソッドを持っているかどうかのチェックですが、Int16などの整数型はメソッドop_で始まるメソッドがないという、少し意外な結果となりました。

検証2

OP.cs
foreach (var t in types) {
    List<MethodInfo> OverloadOperators = t.GetMethods().Where(x => x.Name.StartsWith("op_")).ToList();
    foreach (var m in OverloadOperators) {
        Console.WriteLine($"{t.Name} : {m.Name}");
    }
}
IntPtr : op_Explicit
IntPtr : op_Explicit
IntPtr : op_Explicit
IntPtr : op_Explicit
IntPtr : op_Explicit
IntPtr : op_Explicit
IntPtr : op_Equality
IntPtr : op_Inequality
IntPtr : op_Addition
IntPtr : op_Subtraction
UIntPtr : op_Explicit
UIntPtr : op_Explicit
UIntPtr : op_Explicit
UIntPtr : op_Explicit
UIntPtr : op_Explicit
UIntPtr : op_Explicit
UIntPtr : op_Equality
UIntPtr : op_Inequality
UIntPtr : op_Addition
UIntPtr : op_Subtraction
Double : op_Equality
Double : op_Inequality
Double : op_LessThan
Double : op_GreaterThan
Double : op_LessThanOrEqual
Double : op_GreaterThanOrEqual
Single : op_Equality
Single : op_Inequality
Single : op_LessThan
Single : op_GreaterThan
Single : op_LessThanOrEqual
Single : op_GreaterThanOrEqual
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Implicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_Explicit
Decimal : op_UnaryPlus
Decimal : op_UnaryNegation
Decimal : op_Increment
Decimal : op_Decrement
Decimal : op_Addition
Decimal : op_Subtraction
Decimal : op_Multiply
Decimal : op_Division
Decimal : op_Modulus
Decimal : op_Equality
Decimal : op_Inequality
Decimal : op_LessThan
Decimal : op_LessThanOrEqual
Decimal : op_GreaterThan
Decimal : op_GreaterThanOrEqual
String : op_Equality
String : op_Inequality
DateTime : op_Addition
DateTime : op_Subtraction
DateTime : op_Subtraction
DateTime : op_Equality
DateTime : op_Inequality
DateTime : op_LessThan
DateTime : op_LessThanOrEqual
DateTime : op_GreaterThan
DateTime : op_GreaterThanOrEqual
TimeSpan : op_UnaryNegation
TimeSpan : op_Subtraction
TimeSpan : op_UnaryPlus
TimeSpan : op_Addition
TimeSpan : op_Equality
TimeSpan : op_Inequality
TimeSpan : op_LessThan
TimeSpan : op_LessThanOrEqual
TimeSpan : op_GreaterThan
TimeSpan : op_GreaterThanOrEqual

列挙してみると、キャストも含まれていました。。。

doubleやstringに、足し算を意味する"op_Addition"がありません。
整数や浮動小数点数はオーバーロードではなく、コンパイラがILの専用命令に変換しているからop_Additionは不要のようです。
また、参考のリンク先によるとstringの+は文字列連結のconcatに変換されるらしいので、オーバーロードとして定義されていません。

追伸

ここまでくれば、ILの仕様もしくはコンパイル後のILを覗いてみればいいような気がします。
機会があればILを見てみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

C#におけるcharは整数型なのか?

はじめに

なるほどなっとくC#入門』(はじめてプログラミングを学ぶ人を対象にしたC#の入門書)の読者の方から、こんな指摘がきました。1

C#組み込み型の表でcharが文字型とされていますが正しくは整数型です
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/language-specification/types

確かにこのページには、'char'は integral_typeに分類されています。
この事実は僕も知っていました。

実際、以下のようなコードはビルドエラーにはならず、実行することができます。

char a = 'a';
char b = 'c';
System.Console.WriteLine(a + b);
System.Console.WriteLine(a * b);

といっても、結果は、char型にはなってくれませんが...

C言語でバリバリプログラミングしていた頃は、charが整数型であることを利用したコードもよく書いていました。

しかし、「なるほどなっとくC#入門」では、組み込み型の説明では、整数型とは独立させて、charを以下の表のように文字型として分類しました。

組み込み型の種類  キーワード
整数型 byte, byte, short, short, int, uint, long, long
文字型 char
.... .... 以下略 ...

なぜ、文字型として分類したのか

『なるほどなっとくC#入門』の「はじめに」で僕は以下のように書いています。

文法の解説では、できるだけ正確な表現になるように努めましたが、正確で詳細な文法解説は、退屈であると同時に無用な混乱を招いてしまう恐れがあります。そのため、わかりやすさと正確さのバランスをとりながら説明するようにしました。

つまり、charを整数型に分類し説明することは、初心者には無用な混乱を招くと判断したわけです。

実際に、char型を整数型として扱う場面はほんのわずかであり、整数型とは独立したchar型として説明するほうが、理解が進むというのが僕の考えです。

Microsoft Docsの他のページではどう説明されているのか

これだけでは、僕の独りよがりみたいな意見で終わってしまうので、Microsoft Docsの他のページでは、どのように説明されているのか見てみましょう。

値型 (C# リファレンス) では、以下のように説明されています。

C# には、単純型とも呼ばれる次の組み込み値型が用意されています。
整数数値型
浮動小数点数値型
ブール値を表す bool
Unicode UTF-16 文字を表す char

char型は、整数型とは別に独立させています。

また、C# 型の既定値 (C# リファレンス)では、以下のような記述があります。

種類 既定値
任意の組み込み整数数値型 0 (ゼロ)
char '\0' (U+0000)

整数数値型 (C# リファレンス)では、以下のような記述があります。

C# では、次の定義済みの整数型がサポートされています。

C# 型/キーワード 範囲 サイズ .NET 型
sbyte -128 ~ 127 符号付き 8 ビット整数 System.SByte
byte 0 ~ 255 符号なし 8 ビット整数 System.Byte
short -32,768 ~ 32,767 符号付き 16 ビット整数 System.Int16
ushort 0 ~ 65,535 符号なし 16 ビット整数 System.UInt16
int -2,147,483,648 ~ 2,147,483,647 符号付き 32 ビット整数 System.Int32
uint 0 ~ 4,294,967,295 符号なし 32 ビット整数 System.UInt32
long -9,223,372,036,854,775,808 から 9,223,372,036,854,775,807 符号付き 64 ビット整数 System.Int64
ulong 0 ~ 18,446,744,073,709,551,615 符号なし 64 ビット整数 System.UInt64

この表にはcharは含まれていません。

これらのページでは、charは整数型とは別の型であるという立場をとっているように見えます。

結論

ということで、僕の結論は以下の通りです。

C#の言語仕様的には、char型は整数型に分類されているが、C#を説明する上では、charを整数型とは別の型として説明することは間違いではなく、むしろchar型は整数型とは別の型として説明するべきものである。

もちろん、C#の仕様を理解したエンジニアのかたが、「char型は整数型である」と主張することに異論を唱えるつもりはありません。

ある程度、理解が進んだ方には、「char型は算術演算が可能でint型の結果が生成される」ということを知ることも有意義だと思います。

ただ、C#を教えるという立場からすると、経験の浅いプログラマーが、嬉々としてcharを整数型として使うようなケースが出てくることのほうが怖いなーと思ったりもしてしまいます。


  1. 出版社経由の匿名の読者様ということで直接僕の考えをお伝えすることはできませんでした。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む