トップ回答者
クラスに参照渡してクラス内で変更するには

質問
-
メソッドへの参照渡しは ref や out で行えます。
ではクラスに参照渡しして、そのクラス内で変数に修正を加えるにはどうしたらよいでしょうか?
下記のサンプル FlipFlop は、呼び出し元の bool 変数の値が処理後に逆転することを保証することを目的としています。サンプル FlipFlop
--------------------bool status = false; using (new FlipFlop(ref status )) { DoSomething(); } public class FlipFlop : IDisposable { private bool _disposed = false; private bool preState; public FlipFlop(ref bool _preState) { // 元の状態を保持 preState = _preState; } ~FlipFlop() { Dispose(); } public void Dispose() { if (!_disposed) { // 状態を逆転して戻す preState ^= true; _disposed = true; } GC.SuppressFinalize(this); } }
もちろんサンプルでは、クラス内のローカルな変数の preState が変わるだけで目的を達していません。
解決案として(1)
C# でもポインターが使える。 しかし、 unsafe の制約が入るので、使いたくない。
(2)
bool変数を含んだ構造体を呼び出し元で準備して、それを引数で渡し、 呼び出し先で object で引き取り、処理する。
しかし、ちょっと面倒。 もっと素直に ref bool 変数 で引き渡したい。(3)
コンストラクタで、 引数 ref bool を クラス内変数 object に代入し、 Dispose() において、その obhect に対して ^=true を命令する ???????
このプログラミングがよくわからない。ご回答をお願いします。
high4
回答
すべての返信
-
refやoutでは実現できず、ポインターが必須です。ポインターを使うか設計を変えるかの2択です。またIDisposeableは用途が異なります。using()で書けるためかっこよく見えるかもしれません。しかしDispose()を呼び忘れていた場合、デストラクター実行時にbool値が反転するわけですが、そのデストラクターの実行タイミングは不定なため、いつbool値が反転するか不安定なものとなってしまいます。これは避けるべきです。
そこでAzuleanさんとは別の視点で
FlipFlopクラスがbool値を持つべきです。ついでにboolへの暗黙の型変換なども持たせれば、bool値のように見えるでしょう。
-
まず、処理後に○○する事を保障する場合に利用するべき構文は using/IDisposable ではなく try finally です。(便利だからと using/IDisposable が使われる事も多くないですが)
try
{
DoSomething();
}
finally
{
// do ChangeState
}
ですので、本来はこうなります。bool status = false;
try {
DoSomething();
}
finally
{
status ^= true;
}using / IDisposable を使う場合にはこの finally 句全体を実行内容として渡します。
class Hoge : IDisposable
{
Action _action;
public Hoge( Action action ) { _action=action; }
public void Dispose() { _action(); }
}
bool status = false;
using( new Hoge( ()=>{status^=true;} )
{
DoSomething();
}現実的な実行性能でいいますと、インターフェース経由での呼び出しは JIT インライン化の対象になりませんので、 try/finally で書かれた場合に比べて相当に遅い事を覚悟する必要があり、乱用するべき手段ではない事は認識しておく必要があります。
Kazuhiko Kikuchi -
(3)
コンストラクタで、 引数 ref bool を クラス内変数 object に代入し、 Dispose() において、その obhect に対して ^=true を命令する ???????
このプログラミングがよくわからない。
解決方法については既にいろいろ出ている通りだと思いますので、この(3)についてです。
結論から言えば出来ません。CLRレベルではローカル変数にrefを付けるようなことは可能ですが、C#の言語として実装されていません。理由は、ローカル変数の寿命が短いために、比較的寿命の長いクラスのインスタンスなどから参照されると不具合が起こりやすいからだったと思います。(元ネタがちょっと探せませんが・・・)
★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/ -
Azulean さん、佐祐理さん、kazuk さん、trapemiya さん、回答ありがとうございます。
C++育ちで、C#には初心者の私にとって、この件は当初思ってたより複雑なようです。Azulean さん
-------------
確かに、構造体を参照渡しで渡しても、呼び出し先のメンバ変数に結局は「代入」することになるのでダメでした。
その点、クラスで渡すなら、 ref を付けなくても、呼び出し先での変更が呼び出し元に反映されました。
クラスの場合は、C++ での所謂「アドレス渡し」になるのですから納得できます。public class PackBool { public bool value; } { PackBool packBool = new PackBool(); packBool.value = false; using (new FlipFlop(packBool)) { DoSomething(); } }
佐祐理さん
-------------
> そのデストラクターの実行タイミングは不定なためこの文章にはドキッとしました。 C++ ではありえないことですから。
そこで、「C# デストラクター」で google すると、
http://www.atmarkit.co.jp/fdotnet/csharp_abc/csharp_abc_011/csharp_abc03.html
がみつかりました。
川俣 晶さんの「C#入門」-第11回 コンストラクタとデストラクタ です。要旨は、「デストラクタは、いつ実行されるか分からないもの」、しかし、「IDisposableを使えばDisposeメソッドで確実に終了処理できる」。
つまり、usingステートメントで確実な終了処理をすることを説明しています。kazuk さん
--------------
提示していただいたコードの、ラムダ式を使った using の中の { } の部分でコンパイルエラーを起こします。
理由がわかりません。class Hoge : IDisposable { Action _action; public Hoge(Action action) { _action = action; } public void Dispose() { _action(); } } { bool status = false; using( new Hoge( ()=>{status^=true;} ) { DoSomething(); } }
Try-Finaly の方法は、終了処理部分がアプリのコード部分に見えてくるので、やはりライブラリ化したコードの中に隠したいので、 using を使いたいと思います。
trapemiya さん
---------------
> CLRレベルではローカル変数にrefを付けるようなことは可能ですが、C#の言語として実装されていません。コンストラクタで受けた ref 付きの引数を「参照」のまま保存する方法を探していたのですが、やはりダメでしたか。
C++ の BOOL& status の宣言が C# で出来れば、この問題は簡単に解決できるのですが。
皆さんへ
---------
皆さんからの意見を参考にして、次のように考えたいと思います。(1) クラスに参照渡ししてクラス内で変更するには?
引数にクラスを使う(しかない)。但し、 ref 付きにする必要はない。
(2) 確実に終了処理を行う「流れ」を生み出すには?
IDisposable を使った using で処理する。
厳密には、 using 内の実行部分では try-catch の例外処理を含める。
これで終了処理が確実に実行されることが言語レベルで保障される。補足
-----
クラスによる引数を使う方法は、本当はチョット、ひと手間増えるのと、コードが「直観的ではない」ところがあります。その点、kazuk さんの「Action」が使えれば、逆にもっと汎用的な使い方ができるかもしれません。
kazuk さん、もしよろしければ、上記のコンパイルエラーの理由を教えてください。high4
- 編集済み high4 2011年10月27日 15:03
-
FlipFlop クラスは、何かの処理を、「試みたこと」で反転するのでしょうか。それとも、「完了したこと」で反転するのでしょうか。
え~っと、例示されている使い方では、DoSomething() が成功しようが失敗しようが、using ブロックを抜けると反転していますよね。これが、「何かの処理を試みたことで反転」。DoSomothing() が成功した時だけ反転させたいのなら、「何かの処理が完了したときに反転」。この、どちらなのでしょう?
例えば、FlipFlop にデリゲートを用意して、FlipFlop からメソッドを実行させる。その結果は FlipFlop が知ることが出来るので、完了したときのみ反転させる、というようなことが出来ると思います。
Jitta@わんくま同盟 -
Jitta さんへ
-------------
> FlipFlop クラスは、何かの処理を、「試みたこと」で反転するのでしょうか。
> それとも、「完了したこと」で反転するのでしょうか。これは「完了したことで反転する」を意図しています。
但し、「試みた」とか「成功した」とかではなく、単に処理が終わったら、の意味です。
> 例えば、FlipFlop にデリゲートを用意して、FlipFlop からメソッドを実行させる。この using FlipFlop は using を使った Wait Cursor の処理と同種のものです。
最初の質問で、例示を簡単にするために、DoSomething() と、あたかも1つのメソッドを実行するだけのような印象を与えてしまったので、皆さんに誤解を与えたようです。
こちらの意図は、そこで何かの処理をしてから、bool が反転しますというものでした。単に bool を反転するだけなら
status = false;
DoSomething1();
DoSomething2();
DoSomething3();
status ^= true;
でよいわけです。しかし、DoSomethingN() の部分に色々なコードが入るとすると、例えば仕方なしに、そこで goto なんかを使わざるを得ないとしたら、上記のような羅列処理形式では余計な「状態監視コード」を追加しなければなりません。
その意味では、C# の using という方法はとても「有難い」ものなので、using を使った FlipFlop を選択したわけです。繰り返しになりますが、 using を使った WaitCursor をイメージしていただければ、この FlipFlop が理解していただけると思います。
補足
---------
今回は単に bool で質問しましたが FlipFlop<T> with using を想像していただければ、と思います。
その意味で、kazuk さんの 「Action(s)」に魅力を感じています。実は「Action」のことは知りませんでした。この <T> にラムダが使えるなら、今までにないプログラムスタイルを生み出すものになるかもしれません。
using+<T(s)>+ラムダは人間の思考回路に近いので。
この方向に進むときに、 C# に BOOL& status の宣言がないことがチョットいやな足かせになります。high4
- 編集済み high4 2011年10月28日 12:47
-
これは「完了したことで反転する」を意図しています。
但し、「試みた」とか「成功した」とかではなく、単に処理が終わったら、の意味です。
それでしたら、Task.ContinueWithを使ってみてはいかがでしょう。bool status = false; Task.Factory.StartNew(DoSomething) .ContinueWith(t => status = !status) .Wait();
クラスにするなら、次のようになるでしょうか。using System.Threading.Tasks; class FlipFlop { Func<bool> status; public FlipFlop(Func<bool> status) { this.status = status; } public bool Execute(Action doSomething) { return Task.Factory.StartNew(doSomething) .ContinueWith(t => !status()) .Result; } }
このクラスを次のように使います。bool status = false; status = new FlipFlop(() => status).Execute(DoSomething); //または、次のように書いて戻り値を無視します。 //new FlipFlop(() => status = !status).Execute(DoSomething);
ご参考になれば幸いです。
(追記)上記のクラスの場合、むしろtry~finallyで書いたほうが読みやすいかもしれませんね。public bool Execute(Action doSomething) { bool result; try { doSomething(); } finally { result = !status(); } return result; }
(追記その2)次のように、Action<bool>デリゲートで代入式を渡しておいて、クラス内で呼び出してもいいかもしれません。new FlipFlop(() => status, b => status = b);
- 編集済み Alfred360 2011年10月29日 2:54 追記その2と、コード例のExecuteを短く修正しました。
-
「WaitCursor をイメージ」が何のことかわからないのですが、WinForms のカーソルを変更するような話でしょうか?
私はカーソルの変更は using を使ってやるようまクラスを生成していますが、かなり用途が違うと思います。カーソルの状態変更のクラスは、現在状態を保持するのはクラス自身で完結しているはずです。
ユーザ コードで準備された bool state をカーソルの現在値のように扱いたいということでしょうか? クラスライブラリ側で任意に書き換える可能性がどれぐらいあるかによりますが、Dispose パターンで終了時にユーザ コードに影響を与えたいだけなら、素直に try-finally を使うべきだと思います。 逆に、Dispose 以外の要因によってユーザ コードを任意に実行する必要があるなら、はじめて delegate を使ったコールバックの仕組みが生きてくるでしょう。
ラムダ式を使って delegate を生成するのもよいですが、このようなパターンであれば素直にイベントを使うのがシンプルでよいのではないかと思います。(イニシャライザとの相性が悪いので、記述性だけを求めて delegate でもいいんですけど)
public class FlipFlop : IDisposable { public FlipFlop() { } public FlipFlop(EventHandler e) : this() { this.OnFlip += e; } public event EventHandler OnFlip; public virtual void Flip() { if (this.OnFlip != null) this.OnFlip(this, EventArgs.Empty); } void IDisposable.Dispose() { this.Flip(); GC.SupressFinalize(this); } ~FlipFlop() { if (this.OnFlip != null) throw new InvalidOperation(); } }
こんなイメージ? 殴り書きなのでコンパイルできるかどうかはわかりません。> 上記のコンパイルエラーの理由を教えてください。
コンパイルエラーがかかれていませんし、実際に試してもいないですが、括弧を閉じていないからとかではないですよね?
> 終了処理部分がアプリのコード部分に見えてくるので、やはりライブラリ化したコードの中に隠したいので
何をライブラリ化したいのか次第だと思いますが、ユーザコードとしての終了処理を渡して処理するような仕組みであれば、try-finally を利用するほうがよりよいと思います。現在の構造では、bool 変数も、反転処理もどうみてもアプリケーション側のコードですよね?
ライブラリ的なコードとするならば、ライブラリ側で bool の変数に相当する状態の管理を準備してし、ライブラリの中で反転するように設計するようにするんじゃないでしょうか? 何を隠ぺいして何を使いたいかといったライブラリの外部設計がぶれていませんか?
- 編集済み K. Takaoka 2011年10月29日 3:19 bool はいらないらしいので削除
-
Alfred360 さん、K.Takaoka さん、ご回答ありがとうございます。
当初の「クラスに参照渡ししてクラス内で変更するには」については「クラスを引数に」で、と考えています。
さて、話題が終了処理には using か、それとも try-finally か、になってきています。
Alfred360 さんへ
------------------> それでしたら、Task.ContinueWithを使ってみてはいかがでしょう。
これにはビックリです。これこそ dotNet ライブラリの恩恵です。
このままメモしておきます。
K.Takaoka さんへ
------------------> 「WaitCursor をイメージ」が何のことかわからないのですが、WinForms のカーソルを変更するような話でしょうか?
多分、K.Takaoka さんの思っておられるものです。
それにおっしゃるとおり、WaitCursor自身がその中で、変数を完結しています。public class WaitCursor : IDisposable { private bool _disposed = false; private Cursor preCursor; public WaitCursor() { // 元のカーソルを保持 preCursor = Cursor.Current; // カーソルを待機カーソルに変更 Cursor.Current = Cursors.WaitCursor;
さて、using か、それとも try-finally かは、言語レベルでの終了処理の確実性に差異がないようなので、その選択は、その時のアプリコードの内容によるかと思われます。
少なくとも今回の件で using を選んだ理由は、単に、アプリのコード内に、 status ^= true; の、 ^= true の部分のコードをわざわざ書きたくなかったからです。 using の時点で、 status ^= true; が予期されるので、ただただ余分なコードは書きたくなかっただけです。
その意味で WaitCursor と同じと申し上げたわけです。
これを finally で書こうとすると、
// カーソルを元に戻す
Cursor.Current = preCursor;
と書かなければなりません。このひと手間が好きじゃないのです。
過去において、この何でもない敗戦処理のようなコードで、油断してバグを抱えたことが何度もあります。
まあ、私の稚拙さが本当の原因なのですが。kazuk さん、K.Takaoka さんへ
---------------------------------> コンパイルエラーがかかれていませんし、実際に試してもいないですが、括弧を閉じていないからとかではないですよね?
失礼しました。確かにおっしゃるとおりでした。
ラムダ式はどうも目がチカチカします。これも私の稚拙さです。high4
-
蛇足ですが、Visual Studio 2010のテキストエディタにも類似のバグがあります。テキストエディタ上でのマウスカーソルがIBeam型からArrow型になったまま戻らない。
誰でもやらかす問題だと思いますw
-
おそらく、bool を例にされているのが一番の問題ではないか?と思います。
また、どこからどこまでがライブラリを利用する側の書くコードで、どこからどこまでをライブラリを提供する側の書くコードとしたいのかも、(bool を例にしていることも相まって) 不明瞭になっていると思います。
カーソルの変更を例にすると、私は下記の1または2であるのが望ましいのではないかと書いていますが、high4 さんの文章をみるかぎり、パターン3のようにしたいと主張されているように見えます。そして、このようなパターンを汎用化するために bool 以外にも使える終了処理を探されているとしか見えません。
// パターン1:ライブラリで書き換えてライブラリが戻す using (new ChangeCursor(Cursors.Wait)) { // 時間のかかる処理 } // パターン2:ユーザが書き換えて、ユーザが戻す var savingCursor = Screen.Cursor Screen.Cursor = Cursors.Wait; try { // 時間のかかる処理 } finally { Screen.Cursor = savingCursor; } // パターン3:ユーザが書き換えて、ライブラリが戻す using (new ChangeCursor()) { Screen.Cursor = Cursors.Wait; // 時間のかかる処理 }
※ ChangeCurosr クラスは、new された時のカーソルを覚えておき Dispose 時に戻す
このようなパターンが悪いとは思いませんし、すでにあるように delegate や event を用いて作成することもできます。(ラムダ式や匿名デリゲートを使えば、記述性もある程度確保できます)
using/try-finally の中で外部へ脱出するコードが多々ある場合、終了処理が前方にあるのは可読性を上げる場合もあるでしょうけど、using/try-finally の中身を可読性を落とさないように整理するべきだという意見もあるでしょう。
.NET Framework 自身の非同期処理の設計でも、このように記述する位置と処理順序が前後することがありますが、時期バージョンでは対応がとられる予定になっており、終了処理を前に書かないですむような方向へ変わっていく予定になっています。
- 編集済み K. Takaoka 2011年11月2日 3:56
-
K.Takaoka さん ご意見ありがとうございます。
「終了処理」は、おっしゃる通り、その目的や状態に応じて様々なパターンが必要になるのですから、それぞれに適切な文法の選択をしていけばよろしいかと思います。
> .NET Framework 自身の非同期処理の設計でも、このように記述する位置と処理順序が前後することがありますが、時期バージョンでは対応がとられる予定になっており、終了処理を前に書かないですむような方向へ変わっていく予定になっています。dotNet の世界には初心者の私には、「.NET Framework の非同期処理の設計」の意味することがよくわかりませんが、少なくとも終了処理が前に来るようなコーディングは精神衛生上よくありません。
ただ、using を 「終了処理が前に来るようなコーディング」と言ってしまうと、 using がかわいそうにも思えます。
using は「終了処理の指示」をしているだけなのですから。
今までたくさんの人からご回答をいただきました。
クラスへの参照型引数の渡し方もわかりました。
終了方法の様々なアプローチも知りました。
<T> + Action,, + Lambda というとんでもない汎用型処理文も知りました。ありがとうございました。
high4