C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

空の delegate は null なの?

[トピック内 15 記事 (1 - 15 表示)]  << 0 >>

■86407 / inTopicNo.1)  空の delegate は null なの?
  
□投稿者/ 774RR (586回)-(2018/01/26(Fri) 14:18:29)

分類:[C#] 

なんだかすごく納得がいかないのですが、こういうものなのでしょうか?

class test
{
int f(int x, int y) { return x+y; }
delegate int delegate_ifii(int x, int y);
delegate_ifii d;
public test()
{
// メンバ宣言して new してない段階では d は null なのは納得
d = new delegate_ifii(f); // ここで d が作られるのも納得
d -= f; // デバッガ上 d が null となるのが理解不能
// デバッガ上だけでなくて本当に null の様子
if (d == null) System.Diagnostics.Debug.WriteLine("null");
d += f; // null に対して += できるのが理解不能
}
}

そういやオイラ event ハンドラにはいつも「何もしない」ハンドラを割り振ってますな。
public event PnPEventHandler pnpEvent = delegate(object o, EventArgs e) { };
割り振らないとエラーになるのはこの辺に原因があったのか・・・

引用返信 編集キー/
■86408 / inTopicNo.2)  Re[1]: 空の delegate は null なの?
□投稿者/ 774RR (587回)-(2018/01/26(Fri) 14:19:43)
あう図表モードになってなかった・・・まあいいや適当にインデントしてください。
引用返信 編集キー/
■86418 / inTopicNo.3)  Re[1]: 空の delegate は null なの?
□投稿者/ WebSurfer (1416回)-(2018/01/26(Fri) 17:42:32)
No86407 (774RR さん) に返信

> なんだかすごく納得がいかないのですが、こういうものなのでしょうか?

最後の方にイベントとハンドラのことを書かれていますが、それと同じで、そういうものだと
思います。

ハンドラがアタッチされてない場合、イベントは null になるので、イベントを起動するメソ
ッドではイベントが null でないことを確認した上で起動するというのがお約束になっている
ということを Microsoft のチュートリアルで見た記憶があります。

> d = new delegate_ifii(f); // ここで d が作られるのも納得
> d -= f; // デバッガ上 d が null となるのが理解不能

という操作は、ハンドラをアタッチして、同じハンドラをデタッチしているので、d が null に
なるのは、少なくとも自分は理解できます。
引用返信 編集キー/
■86420 / inTopicNo.4)  Re[2]: 空の delegate は null なの?
□投稿者/ furu (157回)-(2018/01/26(Fri) 17:54:04)
No86408 (774RR さん) に返信

まあ、特殊なんで

public test()
{
    d += f;        //d==nullでも落ちない
    d += f;
    d -= f;
    d -= f;        //dがnullになる
    d -= f;        //d==nullでも落ちない
}

引用返信 編集キー/
■86424 / inTopicNo.5)  Re[2]: 空の delegate は null なの?
□投稿者/ Hongliang (608回)-(2018/01/26(Fri) 19:16:31)
> d += f; // null に対して += できるのが理解不能
演算子オーバーロードは静的メソッドなので、別に左辺がnullでも(型が許す限りは)問題ないです。
stringでもnullに+=できます。
string str = null;
str += "a";
Debug.WriteLine(str); // "a"
引用返信 編集キー/
■86425 / inTopicNo.6)  Re[2]: 空の delegate は null なの?
□投稿者/ 774RR (589回)-(2018/01/26(Fri) 19:16:58)
null は要するに null ですから(なんのこっちゃ)
null でないオブジェクトを操作したら null になっちゃうあたりが超絶気持ち悪くて
null に対して += できるあたりも心ふたぐのです。

オブジェクトはあって中身が空 (List とか Dictionary とかでありがち) なら心落ち着くんですが。

まあお二人が「そんなもの」と仰ってるので C# 業界ではそういうのもありなんだな、と現実逃避しときます。

解決済み
引用返信 編集キー/
■86427 / inTopicNo.7)  Re[1]: 空の delegate は null なの?
□投稿者/ 魔界の仮面弁士 (1556回)-(2018/01/26(Fri) 20:58:05)
2018/01/26(Fri) 21:01:15 編集(投稿者)

# 解決済みマークはそのままにしておきます。

No86407 (774RR さん) に返信
> なんだかすごく納得がいかないのですが、こういうものなのでしょうか?

糖衣構文を紐解いてみると、仕組みが見えてくるかもしれませんよ。


前提知識として、C# のデリゲートは MulticastDelegate の派生クラスとしてコンパイルされます。
// typeof(delegate_ifii).BaseType.ToString() == "System.MulticastDelegate"

そして MulticastDelegate は System.Delegate の派生クラスです。


> d = new delegate_ifii(f);

C# 1.x では、デリゲートインスタンスの割り当ての際に
 d = new delegate_ifii(this.f);
と書いていましたが、C# 2.0 以降では
 d = this.f;
という糖衣構文でも書けるようになりましたよね。


そして
> d += f;
これは要するに、
 d = (delegate_ifii)delegate_ifii.Combine(d, new delegate_ifii(f));
の糖衣構文だったりします。


そもそも、System.Delegate.Combine は、引数に null を渡すことを禁止していません。
null が渡された場合の動作については下記を参照してください。
https://msdn.microsoft.com/ja-jp/library/30cyx32c.aspx
https://msdn.microsoft.com/ja-jp/library/b1eh4771.aspx


このため、delegateInstance1 += delegateInstance2; という処理は、
左辺「delegateInstance1」と右辺「delegateInstance2」の
いずれかあるいは両方が null であったとしても、
何の問題も無いというわけです。
解決済み
引用返信 編集キー/
■86445 / inTopicNo.8)  Re[2]: 空の delegate は null なの?
□投稿者/ 774RR (590回)-(2018/01/29(Mon) 19:06:38)
おお non-static メンバ関数ではなくて static メンバ関数の syntax sugar だったわけですか・・・
C++ では += が static メンバにはなりえないのでそういう認識でいました。
違う言語を同一視するような時点でかなりアレなんですがなるほどです。
# += が Combine の糖衣構文であるとはどこに書いてあるんだろう・・・探す気力なし

this が必須のような構文をしておいて実は static っつーのはずいぶん罪作りな糖衣構文っすね。
オイラもきっちり調べればこういう質問は出なかったのかもしれませんが調べきれませんでした。
# C/C++ なら言語仕様書でも ReferenceSource でも調べたのですが
# C# は本業ではないのでと逃げておく。


解決済み
引用返信 編集キー/
■86453 / inTopicNo.9)  Re[3]: 空の delegate は null なの?
□投稿者/ 魔界の仮面弁士 (1560回)-(2018/01/30(Tue) 18:27:56)
No86407 (774RR さん) に返信
> そういやオイラ event ハンドラにはいつも「何もしない」ハンドラを割り振ってますな。

それって、イベントを提供する側の話でしょうか。
それとも、イベントを利用する側の話でしょうか。
 button1.Click += delegate {}

いずれにしても、EventHandler や EventHandler<TEventArgs> に限った話ですよね。

C# では、戻り値を返すイベントを定義できますが、
この場合、「何もしない ハンドラ」を割り当てるわけには行かないはず。
 public delegate bool FooDelegate(int x);
 public event FooDelegate Foo;


一方 VB.NET の場合、『戻り値を持ったイベント』を定義しようとすると
Visual Basic ではコンパイルエラー(BC31084)となる仕様です。
定義済みのものを利用する分には、VB6 でも VB.NET でも可能なのですが。


実際のところ、複数のメソッド呼び出しを束ねるのはイベントの場合ぐらいで、
ましてや戻り値を持つメソッドを連結させることは非常に稀だとは思いますが、
もしも個々の戻り値を個別に受け取る必要がある場合は、そのデリゲートの
GetInvocationList メソッドを利用できるようになっています。



> // メンバ宣言して new してない段階では d は null なのは納得

未初期化のローカル変数 d に対しては
 d = Method1;
は実行できても、
 d += Method1;
はコンパイルエラーになりますね(CS0165)。当然のことではありますが。



No86445 (774RR さん) に返信
> おお non-static メンバ関数ではなくて static メンバ関数の syntax sugar だったわけですか・・・

ちなみにデリゲートは、インスタンスメソッドの呼び出しに最適化されているため、
静的メソッドの呼び出しだと、効率がかなり悪いそうです。
これについて、カリー化(curried delegate)で回避できます。
http://ufcpp.net/study/csharp/functional/miscdelegateinternal/


> C++ では += が static メンバにはなりえないのでそういう認識でいました。
> this が必須のような構文をしておいて実は static っつーのはずいぶん罪作りな糖衣構文っすね。

delegate 以外でも、
 string s = null;
 s += null;
と書けますけれどね。しかも上記の結果は、null ではなく string.Empty です。

# 上記は『s = System.String.Concat(null, null);』相当


> # += が Combine の糖衣構文であるとはどこに書いてあるんだろう・・・探す気力なし

同じ意味になると、大昔に TechEd あたりで聞いたような気がするのですが、
済みません、私もうろ覚えなので情報ソースは提示できないです。

公式資料として書いたものがあるかどうかまでは分からないので、
糖衣構文と呼べるかどうかは自信がなくなってきました。今更ですが。


ただ言語仕様面で捉えてみると、+= が Combine というよりは、
「a += b」が「a = a + b;」の糖衣構文と捉えた方が良いかと思っています。

「変数 a」の型が、「a + b」の演算が返す型と同じ(あるいは暗黙変換可能)でない場合、
a += b; はコンパイルエラーとなります。

// これはエラー
int a = 0;
a += 0L;

// これは OK
float b = 0F;
b += 0L;


ただしデリゲートが event 化されている場合は話が変わってきます。

イベントは特別扱いされており、デリゲート型変数とは違って、
演算子として += と -= しか使えません。(デリゲートなら + や - や = も使える)
しかも代入式の左辺にしか記述できないという制限付き。

この制限があるため、匿名メソッドやラムダ式を、
 button2.Click += delegate { MessageBox.Show("Test"); };
のように、変数を介さずにイベントハンドラとして直接割り当ててしまうと、
後で解除あるいはクリアできずに難儀することになりますね。


ちなみに上記の += 処理は、
 button2.add_Click(new EventHandler(delegate { MessageBox.Show("Test"); }));
相当の処理としてコンパイルされます。

といっても、C# からの add_Click メソッドの呼び出しは禁止されているのですけれども。
(JScript.NET や PowerShell の場合は、add_Click メソッドを使って割り当てます)
解決済み
引用返信 編集キー/
■86454 / inTopicNo.10)  Re[4]: 空の delegate は null なの?
□投稿者/ 774RR (592回)-(2018/01/30(Tue) 19:22:51)
> それって、イベントを提供する側の話でしょうか。
提供する側っす。
提供する側は常にいる(通信スレッドがいつも走っていてデータ受信のたびにイベントを起こす)
利用する側は常にいるとは限らない( UI スレッド / Form 側は「モニター停止」状態)
なんてときに event handler に -= をしたくなるのですが、提供する側は null チェックなんぞしたくないので
無条件で event を発行するためには「何もないハンドラ」を最初からくっつけちゃうと善哉。

delegate って this+関数ポインタ と単純な割にはすっごく便利で C++ にも標準で欲しかった感じ。
まあ自作してもそんなに難しくないしウチの新人君の課題に出してみよう。

解決済み
引用返信 編集キー/
■86456 / inTopicNo.11)  Re[5]: 空の delegate は null なの?
□投稿者/ 魔界の仮面弁士 (1561回)-(2018/01/30(Tue) 20:01:46)
2018/01/30(Tue) 20:02:37 編集(投稿者)

No86454 (774RR さん) に返信
> なんてときに event handler に -= をしたくなるのですが、提供する側は null チェックなんぞしたくないので
> 無条件で event を発行するためには「何もないハンドラ」を最初からくっつけちゃうと善哉。

コスト的には、null 条件演算子を使った方がお奨めです。

// 要 C# 6 以降
public event EventHandler ThresholdReached;
protected virtual void OnThresholdReached(EventArgs e) { ThresholdReached?.Invoke(this, e); }
解決済み
引用返信 編集キー/
■86459 / inTopicNo.12)  Re[4]: 空の delegate は null なの?
□投稿者/ furu (159回)-(2018/01/30(Tue) 22:05:06)
2018/01/30(Tue) 22:05:52 編集(投稿者)
2018/01/30(Tue) 22:05:43 編集(投稿者)



No86453 (魔界の仮面弁士 さん) に返信
> ただ言語仕様面で捉えてみると、+= が Combine というよりは、
> 「a += b」が「a = a + b;」の糖衣構文と捉えた方が良いかと思っています。

言語仕様(JIS規格X3015:2008)では、x op= yは

x op yの演算の結果がxの型に暗黙に変換される場合、x = x op y
x op yの演算の結果がxの型に明示的に変換され、yがxの型に暗黙的に変換される場合、x = (T)(x = x op y)
 ※Tはxの型,仕様にはシフト演算子の場合の記述もある

>
> 「変数 a」の型が、「a + b」の演算が返す型と同じ(あるいは暗黙変換可能)でない場合、
> a += b; はコンパイルエラーとなります。

byte b = 0;

b += 1; //OK
b = b + 1; //エラー

b <<= 1; //OK
b = b << 1; //エラー

また、x op= y が x = x op yと決定的に違うのは、xの評価が1度だけだということが保証されています。

int[] x = new int[100];
x[new Random().Next(100)] += 7; //new Random().Next(100)は、1度だけ計算

> ただしデリゲートが event 化されている場合は話が変わってきます。

言語仕様で、通常の+=,-=は、「複合代入」ですが、イベントの場合「イベント代入」となっていて
まったくの別物として定義されてますね。
解決済み
引用返信 編集キー/
■86460 / inTopicNo.13)  Re[5]: 空の delegate は null なの?
□投稿者/ Azulean (918回)-(2018/01/30(Tue) 23:12:36)
2018/01/30(Tue) 23:50:01 編集(投稿者)

No86459 (furu さん) に返信
> 言語仕様(JIS規格X3015:2008)では、

あまり知られていないかもしれませんが、Visual Studio のインストール先の VC#、Specifications 以下に Word で C# 言語仕様書が入っているんですよね。
なので、JIS を見に行かなくても HDD に大抵あるんです。
(ただし、5.0 なので最新の言語仕様書ではない)

「7.17 代入演算子」とか「7.17.2 複合代入」とかですかね、今回の場合。


// イベントアクセスは event キーワードで定義された add/remove があるものを指していると思われるので、デリゲートは別ですね。
// 「10.8.1 フィールドのように使用するイベント」あたりで説明されているものだと考えています。
解決済み
引用返信 編集キー/
■86461 / inTopicNo.14)  Re[6]: 空の delegate は null なの?
□投稿者/ furu (160回)-(2018/01/31(Wed) 09:42:28)
No86460 (Azulean さん) に返信
> 2018/01/30(Tue) 23:50:01 編集(投稿者)
>
> ■No86459 (furu さん) に返信
>>言語仕様(JIS規格X3015:2008)では、
>
> あまり知られていないかもしれませんが、Visual Studio のインストール先の VC#、Specifications 以下に Word で C# 言語仕様書が入っているんですよね。
> なので、JIS を見に行かなくても HDD に大抵あるんです。
> (ただし、5.0 なので最新の言語仕様書ではない)

Azulean さん、ありがとうございます。ずっと探していました。
ここでも以前に質問したりしたんですけど、いつの間にかこんなところにあるとは。
HDDは手元のJISの本よりちょっと遠いですが、いろいろ便利になります。
解決済み
引用返信 編集キー/
■86462 / inTopicNo.15)  Re[7]: 空の delegate は null なの?
□投稿者/ Azulean (919回)-(2018/01/31(Wed) 12:27:13)
No86461 (furu さん) に返信
> ここでも以前に質問したりしたんですけど、いつの間にかこんなところにあるとは。

少なくとも 2005 の時点で存在していました。
以降、ずっとあるはずですが、認知度は低いのですよね。
解決済み
引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ