ブログ「サイバー少年」

ブログ「サイバー少年」へようこそ!
小学六年生ごろからプログラミングを趣味にしている高校生のブログです。
勉強したことについての記事などを書いています。フリーソフトも制作、公開しています。
(当ブログについて詳しくは「ブログ概要紹介」を参照)

サイバー少年が作ったフリーソフトは「サイバー少年の作品展示場」へ

C#で値型の“参照”は扱えないのか!

C言語にはポインタがあります。

ポインタを使えば、

int a; a = 100;
int* p = &a;
------------
*p = 200;
------------
printf("%d", a);


このコードは不思議なことに、100でなく200が出力されてしまうというわけです。

C言語にはこんな素敵な(??)機能がありますが、なんとC#にはありません。

ですが、代わりに参照というものが用意されているのです。
詳しくは書きませんが、参照とは安全なポインタのようなものです。

ここまでは予備知識として書きました。





ふと思ったのですが、C言語ではポインタを1つの値として扱うことができます。

つまり、ポインタはint*型等の値であり、int*型等の変数に代入することができるということです。

そこで、同じようにC#にも参照を扱う型があるのかと思ったら、
なんと!ないのです。

細かくいうと、参照型の変数は参照を扱っている型だといえるかもしれません。

しかし、値型の変数への参照を扱うものがC#にはないのです。


…なぜ無いのか!?
その理由らしきものは思い当たったので後述します。



とりあえずC#で無理矢理、値型の変数の参照を扱っている風のことをしたいなら、値型の変数をクラスのメンバにすればいいです。

つまり、値型の変数への参照を扱うというより、値型の変数を内包して参照型っぽくしてしまう感じです。

ボックス化の応用みたいなものですね。


この方法を汎用化したクラスを作ってみました。

class Reference<T>
{
  public T Value;
  public Reference () { }
  public Reference ( T init )
  {
    this.Value = init;
  }
}

これで一応、参照を扱うことと同等な事はできると思うのですが、とても回りくどいです。




では、内包とかではなく、ストレートに値型の参照を扱うことはできないのでしょうか。

実はできるのです。
TypedReference型というものを使用する方法です。

TypedReference型は普通にインスタンス化するだけでは、参照を扱うものとして使用することができません。

TypedReferenceで参照を扱うには、__makerefキーワードと__refvalueキーワードを使用します。

具体的にコードを書くとこうなります。
(対比として右側に記事冒頭で書いたC言語コードを書きます。)

int a = 100; // int a; a = 100;
TypedReference p = __makeref(a); // int* p = &a;
------------------------
__refvalue(p, int) = 200; // *p = 200;
------------------------
Console.Write(a); // printf("%d", a);


C言語のポインタ型は、ポインタが指している値は何型かという情報も持っているわけですが、

TypedReferenceにはその情報はありません。

そのかわりに、__refvalueという、C言語でいう「*」のようなところで型を判別します。

そこがC言語のポインタの使い方と異なる点ですね。

ちなみに、__refvalueの型指定で参照している変数の型と違うものを指定すると実行時エラーになります。

派生クラスを参照していて、__refvalueの型に基底クラスを指定してもエラーになるのでご注意ください。


また、手抜き設計なのか分かりませんが、__makerefにプロパティを与えるとC#コンパイラが異常終了するみたいです。

メチャクチャですね…。


さて、この方法を実に有用な感じで書いてきましたが

実はこの方法、どうやら推奨はされていない雰囲気なのです。


英語なので関係がある内容なのか自信がないですが、StackOverflowにてこんな質問を見つけました。

c# - Why is TypedReference behind the scenes? It's so fast and safe... almost magical! - Stack Overflow
http://stackoverflow.com/questions/4764573/why-is-typedreference-behind-the-scenes-its-so-fast-and-safe-almost-magical

参照を扱える夢のような方法として書いてきましたが、この方法は使わないほうがいいようですね。




以上に挙げた2つの方法はいずれも、回りくどかったり不完全だったりで、

なぜ大きなニーズのありそうな「参照を扱う」という機能がC#には無いのかと思います。


しかし、この機能がC#に無い理由かもしれないことは1つ思い当たりました。

1. 読み取り専用プロパティの参照を作成
2. 参照をたどって、参照先の値を書き換える
3. 読み取り専用プロパティが書き換えられた!

という現象が起きてしまうからです。


作成した参照に読み取り専用か否かの情報をもたせれば、まぁこの現象は回避できるんでしょうけど、

ただただ実装が面倒くさいため、
それならこの記事で1番目に挙げた方法を使えばいいじゃん、
ということになっているのかもしれませんね。


というわけで、現時点ではC#でストレートに参照を扱う方法は無いと考えていいと思います。

使うとしたら、やはり1番目の方法でしょうかね~。

tag: C# 値型 参照

コメント

プロパティはメソッドとして実装されるので、その戻り値はクラスのフィールドではなくスタックに確保される一時変数です。よって TypedReference によるフィールド書き換えは起こりません。
それにしても、実行時エラーではなくコンパイラの異常停止というのは気になるところ。やっぱり手抜きなんですかね

  • 2014/12/19(金) 18:16:52 |
  • URL |
  • 表記なし #-
  • [ 編集 ]

Re: 表記なし

あ~、たしかにプロパティが返すのは値のコピーですから、記事で書いたような問題は起きませんね。
まあただ、readonlyなフィールドの場合はこういう問題が起きそうですが。

readonlyで試したことがないですが、多分これも実行時エラーかコンパイラの異常停止になるんだと思います。
__makerefと__refvalueを使うにしても、対象はローカル変数限定にしたほうがよさそうですね。

にしてもコンパイラの異常停止とは驚きましたね。
この機能は非推奨で隠れているので、開発側としても使用されることを想定していなかったんでしょうか。
それで手抜きになっているということなんでしょうね。

コメントの投稿

トラックバック

トラックバック URL
http://cyberboy6.blog.fc2.com/tb.php/339-5828861c
この記事にトラックバックする(FC2ブログユーザー)

当ブログをご利用(閲覧等)になる場合は必ず「当ブログの利用規定」をお守りください。