C#の言語仕様には、主に2種類のデータ型を変換する手段が用意されている。1つはキャスト演算子(「()演算子」)で、もう1つはas演算子である。どちらを使っても、データ型を変換することができる。
まずキャスト演算子では次のようにして記述する。
object obj = "文字列";
string str = (string)obj;
この例では、object型の参照に対して、“(string)”という表記を使って、string型の参照に変換している。
これに対してas演算子では次のように記述する。
object obj = "文字列";
string str = obj as string;
これもobject型の参照をstring型の参照に変換している。as演算子では、その手前に変換したい参照を、後ろに変換したい型を記述する。キャスト演算子とas演算子の表記は、まったく違って見えるが、それぞれのコードを実行した結果は同じものとなる。
なお、正確には、文字列をobject型変数に代入している最初の行も暗黙的な変換を行っている。objectクラスは文字列型のスーパークラスであるため、このような操作は暗黙的に行える。
キャスト演算子はC言語の流れを汲む多くのプログラム言語で使用でき、C#プログラムでも多用されている。これに対し、as演算子を使用したソース・コードを見かけることは多くない。しかし、キャスト演算子とは別にas演算子が存在するのには、それなりの理由がある。両者は、データ型を変換するという目的は似ているが、機能の詳細は同じではない。
第1の相違は、変換できないときの挙動の相違である。以下のサンプル・プログラムを見ると、それが分かるだろう。
// castandas.cs
using System;
class MyClass {
public static void Main() {
object obj = "文字列";
MyClass cls1 = obj as MyClass;
Console.WriteLine(cls1 == null);
// 出力:True
try {
MyClass cls2 = (MyClass)obj;
} catch(InvalidCastException e) {
Console.WriteLine(e.ToString());
// 出力:
// System.InvalidCastException: 指定されたキャストは有効ではありません。
// at MyClass.Main()
}
}
}
// コンパイル方法:csc castandas.cs
ここでは、string型からMyClass型への、本来は変換不可能な変換を実行させている。
プログラムでは、まず、as演算子による変換を行っている。その結果、cls1 == nullの式がTrueと表示されていることから分かるとおり、変換できないときには、as演算子は結果としてnullを返す。一方、キャスト演算子による変換では、InvalidCastException例外をキャッチしていることから分かるとおり、変換できない場合にはSystem.InvalidCastExceptionという例外を投げる。
プログラムの構造的な都合上、変換できないことをnull値で調べる方がよい場合はas演算子の方が使いやすいだろう。一方、例外で得る方が都合がよい場合はキャスト演算子の方が使いやすいだろう。
キャスト演算子は、値型、参照型を問わずに使用できる。しかし、as演算子は、参照型への変換のみを行うことができる(値型、参照型の詳細については「改訂版 C#入門 5-6 値型と参照型」を参照)。つまり、次のコードは正しくコンパイルできる。
int i = (int)1;
しかし次のように、as演算子で値型のint型への変換を行おうとしているコードはコンパイルできない。
int i = 1 as int;
このコードをコンパイルしようとすると、
as オペレータは参照型で使用してください ( 'int' は値の型です )。
というエラーが起きてしまう。as演算子の後ろには参照型の型のみを記述できる。キャスト演算子にはそのような制約は存在しない。
なお、as演算子は、値型への変換はできないが、値型の値をボックス化によって参照型に変換することは可能である。
C#では、implicitキーワードやexplicitキーワードを使用して、暗黙的あるいは明示的なユーザー定義変換を記述することができる(ユーザー定義変換については「改訂版 C#入門 Column - ユーザー定義のデータ型変換 -」を参照)。キャスト演算子を用いて、このユーザー定義変換を実行させることができるが、as演算子ではできない。
例えば、次の2つのクラスがあるとしよう。何もしないクラス「ClassDoNothing」と、operatorキーワードによりClassDoNothingクラスとの間で型変換を行う機能を含むクラス「ClassImplicit」だ。
class ClassDoNothing {
}
class ClassImplicit {
public static implicit operator
ClassImplicit(ClassDoNothing src) {
return new ClassImplicit();
}
}
これらの2つのクラスに対して、次のようなコードを記述したとする。
ClassDoNothing cdn = new ClassDoNothing();
ClassImplicit ci1 = cdn as ClassImplicit;
しかしこのコードは、as演算子の個所で次のようなエラーが発生し、コンパイルできない。as演算子は、ユーザー定義変換を実行しないためである。
ビルトイン変換で、型 'ClassDoNothing' を 'ClassImplicit' に変換できません。
一方、次のようなキャスト演算子は、ユーザー定義変換により変換可能なので、特にエラーにならず処理可能である。
ClassDoNothing cdn = new ClassDoNothing();
ClassImplicit ci2 = (ClassImplicit)cdn;
もう1つの注意していただきたいのは、次のような場合である。
ClassDoNothing cdn = new ClassDoNothing();
ClassImplicit ci3 = cdn;
この代入には、キャスト演算子も何も変換に関する指示が書かれていない。それにもかかわらずエラーにならないのは、ここで使用しているユーザー定義変換がimplicitキーワードを使った暗黙的な変換であるためである。それにより無関係であるクラス間で、キャスト演算子などを記述することなく代入が可能になっているのだ。しかし、このような変換が可能である状況でも、上述したようにas演算子による変換はエラーになる。すなわち、as演算子はユーザー定義の変換が実行されると困る場合に使用すると有用である。
ここまでの説明で、キャスト演算子と比較してas演算子は実行できる機能が少ないことがお分かりだろうか。そのことは、単なるデメリットばかりではない。実は機能が少ない分だけ、キャスト演算子よりもas演算子を用いた方が処理は高速になる可能性が存在する。以下は、キャスト演算子を使用した場合と、as演算子を使用した場合の速度を比較するサンプル・プログラムである。
// castbench.cs
using System;
class CastBench {
class Sample1 {
public int n = 1;
}
class Sample2 {
}
private static int testCast(object [] testdata) {
int sum = 0;
foreach (object o in testdata) {
try {
sum += ((Sample1)o).n;
} catch(InvalidCastException) {
sum += 1;
}
}
return sum;
}
private static int testAs(object [] testdata) {
int sum = 0;
foreach (object o in testdata) {
Sample1 s = o as Sample1;
if (s != null) {
sum += s.n;
} else {
sum += 1;
}
}
return sum;
}
private static int testIsCast(object [] testdata) {
int sum = 0;
foreach (object o in testdata) {
if (o is Sample1) {
sum += ((Sample1)o).n;
} else {
sum += 1;
}
}
return sum;
}
public static void Main() {
object [] testdata = new object[1000000];
for (int i = 0; i < testdata.Length; i++) {
if (i % 1000 != 0) {
testdata[i] = new Sample1();
} else {
testdata[i] = new Sample2();
}
}
DateTime startCast = DateTime.Now;
for (int i = 0; i < 1000; i++) testCast(testdata);
DateTime endCast = DateTime.Now;
Console.WriteLine("キャストの実行時間 {0}", endCast - startCast);
// 出力例:キャストの実行時間 00:00:47.9899632
DateTime startAs = DateTime.Now;
for (int i = 0; i < 1000; i++) testAs(testdata);
DateTime endAs = DateTime.Now;
Console.WriteLine("as演算子の実行時間 {0}", endAs - startAs);
// 出力例:as演算子の実行時間 00:00:11.7771696
DateTime startIsCats = DateTime.Now;
for (int i = 0; i < 1000; i++) testIsCast(testdata);
DateTime endIsCast = DateTime.Now;
Console.WriteLine("is演算子+キャストの実行時間 {0}", endIsCast - startIsCats);
// 出力例:is演算子+キャストの実行時間 00:00:20.7702804
}
}
// コンパイル方法:csc castbench.cs
コメントとしてプログラム中に埋め込んだ実行結果は、筆者のコンピュータ(Pentium4/1.5GHzのシステム)で実行したときのものである。実行する環境によって、表示される経過時間は変化する可能性がある。
このサンプル・プログラムでは、Sample1クラスとSample2クラスのインスタンスを含むobject型の配列を用意している。この例では、1000個に1個の割合で、変換が失敗するデータを含めている。そして、Sample1クラスならメンバ変数の値を加算し、それ以外なら1を加算する処理を行うメソッドを、キャスト演算子とas演算子で実現してみた。プログラムでは3パターンの計測を行っている。最初のパターンがキャスト演算子によって加算処理を行う例、2つ目のパターンがas演算子によって加算処理を行う例である。実行結果を見て、両者の実行時間を比較すると、as演算子を使った方がはるかに高速であることが分かる。
ここで、例外は重い処理であるという事実に気付いた読者は、キャスト演算子を使った例は例外を処理しているから遅いのかもしれないと思ったかもしれない。実際、変換できないデータの割合を増やせば増やすほど、処理時間は大きく増えてしまうのである。ならば、例外が起きないように処理すればキャスト演算子を使っても遅くならないかもしれない、と考えるのは当然のことだろう。それではということで、キャスト演算子する前にis演算子で型を判定するようにしたのが、3つ目のパターンである。この方式なら例外は投げられない。しかし、実行結果を見ると分かるとおり、確かにキャスト演算子を使ったメソッドよりもはるかに高速になったが、as演算子を使ったメソッドに比べるとかなり遅い。その理由は、is演算子とキャスト演算子によって、2回もデータ型のチェックが入ることになり、処理全体としては重くなってしまったためではないかと考えられる。
また、変換できないデータをまったく含まない配列を用意して実行させても、キャスト演算子を使うメソッドよりも、as演算子を使うメソッドの方がやや高速である。機能が少ない分だけ、as演算子の方が速く処理可能ということができるだろう。
カテゴリ:C# 処理対象:型変換
使用キーワード:キャスト演算子
使用キーワード:as演算子
使用キーワード:is演算子
Copyright© Digital Advantage Corp. All Rights Reserved.