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

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

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

Re[11]: C#における値渡しと参照渡しのコーディングについて


(過去ログ 71 を表示中)

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

■41320 / inTopicNo.1)  C#における値渡しと参照渡しのコーディングについて
  
□投稿者/ simano (8回)-(2009/09/17(Thu) 20:18:03)

分類:[C#] 

こんにちは。
C#における値渡しと参照渡しの利用方法について悩んでいます。

「値渡し」と「参照渡し」について、C#の言語仕様では、
メソッドの引数に構造体を渡した場合は「値渡し」となり、
クラスを渡した場合は「参照渡し」となるとあります。

しかし、実際には、「値渡し」「参照渡し」というコーディングする上で
頻発する場面で、毎回、対象がクラスか構造体かを調べるのは
わずらわしい作業だと思われます。
また、不用意にコーディングすれば、バグの原因にもなりそうです。

例えば、"System.Drawing.Point"をうっかりクラスだと思って引数に渡したら、
構造体だった…という事態が起こりそうです。
また、自作クラスを、途中から構造体に変えたり、その逆にする可能性もありますよね。

考えられる対処方法としては、引数の対象がなんであれ、
基本クラスや構造体以外は、必ずrefを使用するというものがありますが、
少し冗長かなと思います。

皆さんは、この問題をどのようにクリアしているのでしょうか。
よろしくお願いします。
引用返信 編集キー/
■41321 / inTopicNo.2)  Re[1]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ Jitta on the way (434回)-(2009/09/17(Thu) 20:31:32)
No41320 (simano さん) に返信

> しかし、実際には、「値渡し」「参照渡し」というコーディングする上で
> 頻発する場面で、

頻発しますか?まず、使いませんが?


何のために使い分けますか?C#のクラスが「参照型」であることを知れば、その意見はまた変わるのではないですか?
引用返信 編集キー/
■41324 / inTopicNo.3)  Re[2]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simano (9回)-(2009/09/17(Thu) 20:49:42)
No41321 (Jitta on the way さん) に返信
> ■No41320 (simano さん) に返信
>
>>しかし、実際には、「値渡し」「参照渡し」というコーディングする上で
>>頻発する場面で、
>
> 頻発しますか?まず、使いませんが?
>
>
> 何のために使い分けますか?C#のクラスが「参照型」であることを知れば、その意見はまた変わるのではないですか?

重複しますが、
以下の場合が考えられないでしょうか。

・クラスまたは構造体(System.Drawing.Point等)を関数の引数に渡して、内部で変更する。
・自作クラスの深いコピーを行う。
・自作クラスを関数の引数に渡す。
・上記の自作クラスを、後で(スピードアップなどを理由に)構造体に変える。

こういう処理は、C#では一般的ではないのでしょうか?
何か仕様を考え違いしていたら申し訳ないです。
引用返信 編集キー/
■41326 / inTopicNo.4)  Re[3]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ Jitta on the way (435回)-(2009/09/17(Thu) 21:07:48)
No41324 (simano さん) に返信

元の質問を一部読み違っていました。


> メソッドの引数に構造体を渡した場合は「値渡し」となり、
> クラスを渡した場合は「参照渡し」となるとあります。

どこに書いてありました?どちらも値渡しのはずです。


バス停が近いので、これでごめんなさい
引用返信 編集キー/
■41328 / inTopicNo.5)  Re[4]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simano (10回)-(2009/09/17(Thu) 21:35:39)
No41326 (Jitta on the way さん) に返信
> ■No41324 (simano さん) に返信
>
> 元の質問を一部読み違っていました。
>
>
>>メソッドの引数に構造体を渡した場合は「値渡し」となり、
>>クラスを渡した場合は「参照渡し」となるとあります。
>
> どこに書いてありました?どちらも値渡しのはずです。
>
>
> バス停が近いので、これでごめんなさい

説明が悪かったらすみません。
以下を参考にしました。
http://msdn.microsoft.com/ja-jp/library/8b0bdca4%28VS.100%29.aspx

実際に試してみましたが、やはり、クラスの内部変数の値は変更されますが、
構造体の内部変数の値は変更されないようです。

----------
■コード
static void Main(string[] args)
{
MyClassPoint classp = new MyClassPoint();
Console.WriteLine("classp.X = {0}, classp.Y = {1}", classp.X, classp.Y);
new ChangeManager().Change(classp);
Console.WriteLine("classp.X = {0}, classp.Y = {1}", classp.X, classp.Y);

MyStructPoint structp = new MyStructPoint();
Console.WriteLine("structp.X = {0}, structp.Y = {1}", structp.X, structp.Y);
new ChangeManager().Change(structp);
Console.WriteLine("structp.X = {0}, structp.Y = {1}", structp.X, structp.Y);

Console.ReadLine();
}

class MyClassPoint
{
public int X;
public int Y;
}

struct MyStructPoint
{
public int X;
public int Y;
}

class ChangeManager
{
public void Change(MyClassPoint p)
{
p.X = 1;
p.Y = 2;
}

public void Change(MyStructPoint p)
{
p.X = 1;
p.Y = 2;
}
}
----------
■結果
classp.X = 0, classp.Y = 0
classp.X = 1, classp.Y = 2
structp.X = 0, structp.Y = 0
structp.X = 0, structp.Y = 0
----------

普通はクラスを作るから問題ないと思うんですが、たまたま最初にテストしたときに
System.Drawing.Pointを使い、意図と結果が違ってしまったので、
(実際は構造体だが、クラスだと思って使った)
同じ事態が大量のコードの中で起きたら辛いなと・・・。



引用返信 編集キー/
■41329 / inTopicNo.6)  Re[1]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ 魔界の仮面弁士 (1297回)-(2009/09/17(Thu) 21:44:21)
No41320 (simano さん) に返信
> しかし、実際には、「値渡し」「参照渡し」というコーディングする上で
> 頻発する場面で、

そもそも「頻発」するかどうかと問われると、
ほとんどの場合、値渡ししか使わないと思います。参照渡しが必要になるケースは稀かと。
実際、.NET Framework を見ても、"out" や "ref" を伴うメソッドは、そう多くはないですし。


基本前提として
  値渡し では、メソッド内で仮引数のオブジェクトを差し替えても、実引数(呼び出し元の変数)は『同じオブジェクトのまま』。
 参照渡しでは、メソッド内で仮引数のオブジェクトを差し替えると、実引数(呼び出し元の変数)も指し替わる。
だと思います。



≪参照型を【値渡し】する≫
t = this.textBox1;
ByValMethod(t);
 → (メソッド内で、Text プロパティの内容が編集される可能性はあるかも知れないが)
   メソッド実行後に、『t が別のテキストボックスを指している可能性は無い』。同じまま。

≪参照型を{参照渡し}する≫
t = this.textBox1;
ByRefMethod(ref t);
 → メソッド実行後に、t が別のテキストボックスを指している可能性が高い。
  (t のインスタンスを差し替えないのであれば、値渡しで充分)

≪値型を【値渡し】する≫
p = this.textBox1.Location;
ByValMethod(p);
 → 値のコピーを渡しているのだから、『p が別の値になっている可能性は無い』。同じまま。

≪値型を{参照渡し}する≫
p = this.textBox1.Location;
ByRefMethod(ref p);
 → メソッド実行後に、p に別の位置が格納されている可能性が高い。
  (p の値を入れ替えないのであれば、値渡しで充分)
 → メソッドによる値の修正を目的とするなら、p = Foo(p); の方が意図が伝わりやすい。

引用返信 編集キー/
■41340 / inTopicNo.7)  Re[5]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ Jitta on the way (436回)-(2009/09/18(Fri) 07:39:35)
No41328 (simano さん) に返信
> ■No41326 (Jitta on the way さん) に返信
>>■No41324 (simano さん) に返信
>>
>>元の質問を一部読み違っていました。
>>
>>
> >>メソッドの引数に構造体を渡した場合は「値渡し」となり、
> >>クラスを渡した場合は「参照渡し」となるとあります。
>>
>>どこに書いてありました?どちらも値渡しのはずです。
>>
>>
>>バス停が近いので、これでごめんなさい
>
> 説明が悪かったらすみません。
> 以下を参考にしました。
> http://msdn.microsoft.com/ja-jp/library/8b0bdca4%28VS.100%29.aspx

なるほど

This example shows that when a struct is passed to a method, a copy of the struct is passed, but when a class instance is passed, a reference is passed.

これは(^^;
構造体は「値型」で、クラスは「参照型」だと知ってないと、混乱するかも(^^;

あとは、魔界の仮面弁士さんのとおりです。
引用返信 編集キー/
■41355 / inTopicNo.8)  Re[2]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simano (11回)-(2009/09/18(Fri) 11:59:50)
No41329 (魔界の仮面弁士 さん) に返信
No41340 (Jitta on the way さん) に返信

どうもありがとうございました。

C#では基本的に値渡しであり、参照渡しは頻発されないのですね。
今後のコーディングの参考にさせていただきます。

ただそれでも、以下のように、どうしても参照渡しを行いたい場合もあると思います。
------------
■「深いコピー」でデータをコピーする場合
// MyDataクラス内メソッド
public void CopyTo(MyData data)
{
data.MyClass = MyClass; // Class(プロパティ)を設定。値は「参照渡し」される。
data.MyStruct = MyStruct; // Struct(プロパティ)を設定。値は「値渡し」される。
}

(「深いコピー」を行う場合、インスタンスを新しく作成して戻り値にする方が
一般的かもしれません。
ですが、利便性から、参照渡しでコピーしたいことが多いです。)
------------
■ネットワーク通信などで、エラー検出とデータの読み込みを同時に行いたい場合
C#には検査例外がないという理由から、
エラー結果を検出する手段として、メソッドの戻り値を使うことが一般的なようです。

そうなると必然的に、読み込みデータを取得したい場合は、引数からの参照渡しを利用することに
なると思います。

※以下のサイト様を参考にさせていただきました。
http://blogs.msdn.com/nakama/archive/2009/01/09/net-java.aspx

----------
public bool ReadSQLInfo(MySQLInfo info)
{
// 略
try {
// SQLデータの読み込み
}
catch (SqlException sqle)
{
if (sqle.Number == 2627)
{
return false;
}
else
{
throw;
}
}
info = Something_SQLValue;

return true;
}
----------


まとめますと、
コーディングの際は、以下の点に十分注意しないと、後々の悲劇につながりそうだと思いました。

・参照渡しは基本的に使わない。
・参照渡しを使う必要のある場合は、対象がクラスでも構造体でも必ず"ref","out"を使うように徹底する。


今回のトピックを立てた一番の動機は、C#では、一見して、クラスと構造体の見分けが
つかないのに、参照型、値型と扱いがまるで違うのが怖いなと感じたためでした。

単にJavaやC++を先に学んだために、ひっかかるのかもしれませんが…。


引用返信 編集キー/
■41356 / inTopicNo.9)  Re[3]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ 渋木宏明(ひどり) (1207回)-(2009/09/18(Fri) 13:13:09)
渋木宏明(ひどり) さんの Web サイト
2009/09/18(Fri) 13:44:22 編集(投稿者)
2009/09/18(Fri) 13:42:51 編集(投稿者)
2009/09/18(Fri) 13:41:52 編集(投稿者)

> data.MyClass = MyClass; // Class(プロパティ)を設定。値は「参照渡し」される。
> data.MyStruct = MyStruct; // Struct(プロパティ)を設定。値は「値渡し」される。

微妙に違います。
弁士さんの解説を読み直してみてください。

> エラー結果を検出する手段として、メソッドの戻り値を使うことが一般的なようです。

なことないです。
標準クラスライブラリに含まれているクラス/コンポーネントの類は、エラーを例外で表現しています。

> public bool ReadSQLInfo(MySQLInfo info)

こんな書き方(成否を bool で返す)しません。
ReadSQLInfo は、MySQLInfo のインスタンスを返すように設計します。

また、これは検査例外の有無とは関係ないように思います。
通信エラーは業務ロジック上許容できるものではないはずなので、こういう場合は SQLException は放置して、最上流でログに記録するとかの対応をとります。

検査例外の有無なんて本質的にはどーでもいいことです。
業務ロジック上想定される例外なら try ~ catch してしかるべき処置を行う(=Java の場合でも、結局再 throw するケース多くありませんか?)べきだし、想定外の例外なら全く触れることなく上流に処置を任せるべきです。

>単にJavaやC++を先に学んだために、ひっかかるのかもしれませんが…。

Java や C++ を、C 言語的な目線で習得したような印象を受けます。
引用返信 編集キー/
■41366 / inTopicNo.10)  Re[4]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simano (12回)-(2009/09/18(Fri) 14:49:15)
No41356 (渋木宏明(ひどり) さん) に返信
>> data.MyClass = MyClass; // Class(プロパティ)を設定。値は「参照渡し」される。
>> data.MyStruct = MyStruct; // Struct(プロパティ)を設定。値は「値渡し」される。
>
> 微妙に違います。
> 弁士さんの解説を読み直してみてください。

確かに、微妙に違ってますね;すみません。
ですが、「参照渡しを行いたい需要もある」、
という点では変わらないのではないでしょうか。
コーディングの癖を直せばいいといえばそれまでなのですが。

>>■ネットワーク通信などで、エラー検出とデータの読み込みを同時に行いたい場合
>>C#には検査例外がないという理由から、
>>エラー結果を検出する手段として、メソッドの戻り値を使うことが一般的なようです。
>
> なことないです。
> 標準クラスライブラリに含まれているクラス/コンポーネントの類は、通信エラーを例外で表現しています。
>
>> public bool ReadSQLInfo(MySQLInfo info)
>
> こんな書き方(成否を bool で返す)しません。
> ReadSQLInfo は、MySQLInfo のインスタンスを返すように設計します。
>

勉強不足ですみません。
ネットワーク通信の結果を例外のみに任せるべきということですね。
ただ、それは、検査例外がない言語では、扱いにくいのでは?と考えた結果が上記の処理でした。

例えば、サーバと通信して、「実データ」と「結果値」を受ける仕様になっているとします。
「結果値」が「クライアントから受けた値が不正です」だった場合の処理を書いてみました。
----------
public MyData GetFromServer(int arg)
{
// 略
try {
// サーバとのソケット通信。引数に"arg"を渡す。
}
catch (Exception e)
{
throw;
}

MyData data = new MyData();
data = Server_DataBytes_Value; // サーバの返した実データを渡す。

int val = Server_Return_Value; // サーバの返した結果値を渡す。
if (val == ILLEGAL_ARG) // 「クライアントから受けた値が不正です」などのエラー値。
{
throw new MyServerException(val); // 自作の例外クラス
}
}
----------

上記の場合、通信を行っているクラスがアプリケーションの直下にあればいいですが、
dllだったら例外のパターンを把握するのも一苦労だと思います。
後でいつのまにか例外の種類が変わってたらcatchできません。

そこで、「結果値」は、戻り値として返す処理が適切なのかなと思いました。

論点がずれてしまいました…;
例外処理については、自分の中でまだ未消化でして、思慮が浅いのだと思います。
上記の考え違いがありましたら、ご指摘願えないでしょうか。

>Java や C++ を、C 言語的な目線で習得したような印象を受けます。

いえ、C言語はほとんどやってません。
それだけに、Javaに似てるのかと思ったら、内実はC言語の流れなのか…?等々悩んでるうちに、
だんだん変な方向に突き進んでいるのかもしれません;

引用返信 編集キー/
■41374 / inTopicNo.11)  Re[5]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ 渋木宏明(ひどり) (1208回)-(2009/09/18(Fri) 16:26:29)
渋木宏明(ひどり) さんの Web サイト
>ですが、「参照渡しを行いたい需要もある」、
>という点では変わらないのではないでしょうか。

弁士さんも指摘してましたが

・代入操作における参照型/値型それぞれのふるまい
・メソッド引数の参照渡しと値渡し

がごっちゃになっているんじゃないでしょうか。

>ネットワーク通信の結果を例外のみに任せるべきということですね。

ネットワーク通信に限らず、です。
.NET では「エラー」はすべて例外で表現するのが原則です。

int.TryParse() などは特例に見えるかもしれません。
ですが、この類のメソッドに求められている責務は「パース可能かどうかを(bool 値で)返す」ことなのでで、これが自然な実装です。

>ただ、それは、検査例外がない言語では、扱いにくいのでは?と考えた結果が上記の処理でした。

関係ないと思いますねぇ。

>上記の場合、通信を行っているクラスがアプリケーションの直下にあればいいですが、

クラスがどのパッケージに配置されいるのかはあまり関係ないと思いますけど?

>後でいつのまにか例外の種類が変わってたらcatchできません。
>そこで、「結果値」は、戻り値として返す処理が適切なのかなと思いました。

結果値の意味が変わったり拡張されたりしたら、同じことじゃありませんか?

結局、その辺は使用するクラスの仕様をきっちりおさえておくことでしか対応できないです。
検査例外があれば少しは助けになることもあるとは思いますが、それでもリフレクションなんかには対応しきれていないわけで、「検査例外があるからエラー処理は万全」というものでもありません。

>>Java や C++ を、C 言語的な目線で習得したような印象を受けます。
>
>いえ、C言語はほとんどやってません。

そうですか。
でも、Java をしっかりやっていたとしても、検査例外がないくらいでああいったメソッド設計を持ち出してくるというのは、僕的にはすごく不思議な方向性に見えます。

検査例外に対する理解というか受け取り方も、なんかちょっと僕のイメージとは違うものを感じます。

引用返信 編集キー/
■41376 / inTopicNo.12)  Re[6]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simano (13回)-(2009/09/18(Fri) 16:30:42)
No41374 (渋木宏明(ひどり) さん) に返信
> >ですが、「参照渡しを行いたい需要もある」、
> >という点では変わらないのではないでしょうか。
>
> 弁士さんも指摘してましたが
>
> ・代入操作における参照型/値型それぞれのふるまい
> ・メソッド引数の参照渡しと値渡し
>
> がごっちゃになっているんじゃないでしょうか。
>
> >ネットワーク通信の結果を例外のみに任せるべきということですね。
>
> ネットワーク通信に限らず、です。
> .NET では「エラー」はすべて例外で表現するのが原則です。
>
> int.TryParse() などは特例に見えるかもしれません。
> ですが、この類のメソッドに求められている責務は「パース可能かどうかを(bool 値で)返す」ことなのでで、これが自然な実装です。
>
> >ただ、それは、検査例外がない言語では、扱いにくいのでは?と考えた結果が上記の処理でした。
>
> 関係ないと思いますねぇ。
>
> >上記の場合、通信を行っているクラスがアプリケーションの直下にあればいいですが、
>
> クラスがどのパッケージに配置されいるのかはあまり関係ないと思いますけど?
>
> >後でいつのまにか例外の種類が変わってたらcatchできません。
> >そこで、「結果値」は、戻り値として返す処理が適切なのかなと思いました。
>
> 結果値の意味が変わったり拡張されたりしたら、同じことじゃありませんか?
>
> 結局、その辺は使用するクラスの仕様をきっちりおさえておくことでしか対応できないです。
> 検査例外があれば少しは助けになることもあるとは思いますが、それでもリフレクションなんかには対応しきれていないわけで、「検査例外があるからエラー処理は万全」というものでもありません。
>
> >>Java や C++ を、C 言語的な目線で習得したような印象を受けます。
> >
> >いえ、C言語はほとんどやってません。
>
> そうですか。
> でも、Java をしっかりやっていたとしても、検査例外がないくらいでああいったメソッド設計を持ち出してくるというのは、僕的にはすごく不思議な方向性に見えます。
>
> 検査例外に対する理解というか受け取り方も、なんかちょっと僕のイメージとは違うものを感じます。
>

全体的に、了解しました。
一度じっくりと考え直す必要があるようです。
長々とつきあっていただき、ありがとうございました。

C#の方向性に疑問をもっているわけではないです。
怒らせてしまったら(?)申し訳ないです。
解決済み
引用返信 編集キー/
■41377 / inTopicNo.13)  Re[5]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ 魔界の仮面弁士 (1302回)-(2009/09/18(Fri) 16:38:39)
2009/09/18(Fri) 17:54:07 編集(投稿者)
No41366 (simano さん) に返信
> ですが、「参照渡しを行いたい需要もある」、

引数の型が値型であるか参照型であるかに関わらず、
引数に参照渡しを必要とする場合というのは通常、
No41329 のパターンにほぼ限られるかと思います。

それ以外のパターンだと、P/Invoke の場合ぐらいでしょうか。


> 例えば、サーバと通信して、「実データ」と「結果値」を受ける仕様になっているとします。
> 「結果値」が「クライアントから受けた値が不正です」だった場合の処理を書いてみました。
> (…略…)
> 上記の場合、(…略…)例外のパターンを把握するのも一苦労だと思います。
例外が嫌なら、「他の方法で通知する」か「失敗の理由は通知しない」のいずれかかと。

GetFromServer メソッドの利用側に、どのような記述を行わせるのであれば、
「一苦労しない」方法になると思いますか?

---------------
 // 出力引数
 ResultStatus status;
 MyData result = obj.GetFromServer(0, out status);
 if(status == ResultStatus.InvalidData) {
  MessageBox.Show("失敗1");
  return;
 }
---------------
 // 戻り値(値型)での判定
 MyData resultStruct = obj.GetFromServer(0);
 if(resultStruct.HasError) {
  MessageBox.Show("失敗");
  return;
 }
---------------
 // 戻り値(参照型)での判定
 MyData resultClass = obj.GetFromServer(0);
 if(resultClass == null ) {
  MessageBox.Show("失敗");
  return;
 }
---------------
 // エラープロパティでの判定
 MyData result = obj.GetFromServer(0);
 if(obj.LastDllError != Win32Error.NO_ERROR ) {
  MessageBox.Show("失敗:" + obj.LastDllError);
  return;
 }
---------------
 // エラー通知イベントでの捕捉
 obj.OnError += delegate { MessageBox.Show("失敗"); }; // エラー理由はイベント引数で
 MyData result = obj.GetFromServer(0);
---------------
 // 例外処理
 MyData result;
 try {
  result = obj.GetFromServer(0);
 } catch(……) {
  if(!Recovery(〜)) throw;
 } catch(……) {
  MessageBox.Show("失敗");
  return;
 }
---------------

# 他にも幾つかのパターンが考えられるかと思います。


> 「結果値」が「クライアントから受けた値が不正です」だった場合の処理を書いてみました。
結局、最後まで MyData が return される事が無いので、そもそもこのままだと
コンパイルエラーになってしまうかと思いますが、それはさておき。


>   catch (Exception e)
>   {
>     throw;
>   }
この catch 句は無意味だと思いますが……この場合の try/catch は、
どのような動作を目的として記述されているのでしょうか?


>   MyData data = new MyData();
>   data = Server_DataBytes_Value; // サーバの返した実データを渡す。
この記述からして、Server_DataBytes_Value とは、
MyData 型のフィールド変数のようですね。

この場合、最初に割り当てたインスタンスが意味をなしていません。
単純に「MyData data = Server_DataBytes_Value;」で良いかと。


>   int val = Server_Return_Value; // サーバの返した結果値を渡す。
>   if (val == ILLEGAL_ARG) // 「クライアントから受けた値が不正です」などのエラー値。
>   {
>       throw new MyServerException(val); // 自作の例外クラス
>   }
上記の Server_Return_Value は、int 型のフィールド変数ですかね。
比較対象の ILLEGAL_ARG は…… int 型の定数でしょうか?

ここで、変数名を一旦 val に受け直している意図は分かりませんが
(変数名が分かりにくくなっただけのような…?)、何にせよ、
この『GetFromServer メソッド』というものは、
「サーバーと通信して MyData を返す。返せないときは例外を発生。」
という目的で実装されたメソッドという事になるかと思います。

で、その例外のひとつに、「new MyServerException(ILLEGAL_ARG)」がある、と。


> そこで、「結果値」は、戻り値として返す処理が適切なのかなと思いました。
例外だと把握しきれなくて、ILLEGAL_ARG などのint 型(を戻り値や出力引数)で
返す場合には把握することができる…という事にはならないかと。


> dllだったら例外のパターンを把握するのも一苦労だと思います。
それは例外に限らず、どのような返し方であっても一緒では無いでしょうか。

戻り値にしろ、出力引数にしろ、例外にしろ、それらに変更があったのであれば、
利用側にも修正が必要になるでしょうし、利用側に影響を与えたくないのであれば、
内部実装は変更しても、出力情報に対しては手を加えないようにするべきかと思います。


> 後でいつのまにか例外の種類が変わってたらcatchできません。
「例外の種類を変える」とは、どういう意味でしょうか?

・ILLEGAL_ARG の意味が変わったので、利用側が MyServerException.val を利用できなくなった。
・MyServerException の例外が、サーバーエラー以外に対しても throw されるようになった。
・MyServerException を廃止して、別の例外が throw されるように実装し直した。
・MyServerException だけでなく、他の新たな例外も throw される可能性が出てきた。
・素の MyServerException でなく、その継承クラスを throw させることになった。

引用返信 編集キー/
■41382 / inTopicNo.14)  Re[6]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ Jitta on the way (439回)-(2009/09/18(Fri) 17:58:18)
(面倒なのでぶら下げ)

「検査例外がない」のは、「事前に検査をしておけ」とも言えるのではないでしょうか。
しかし、ファイルの有無、ネットワーク接続等、状況が常に変化しており、事前に検査することが難しいものがあります。こういうものは全て「エラー」といってよいでしょう。
引用返信 編集キー/
■41389 / inTopicNo.15)  Re[7]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simano (14回)-(2009/09/18(Fri) 19:24:13)
No41377 (魔界の仮面弁士 さん) に返信
> 2009/09/18(Fri) 16:45:16 編集(投稿者)
>
> > ですが、「参照渡しを行いたい需要もある」、
>
> 引数の型が値型であるか参照型であるかに関わらず、
> 引数に参照渡しを必要とする場合というのは通常、
> No41329 のパターンにほぼ限られるかと思います。
>
> それ以外のパターンだと、P/Invoke の場合ぐらいでしょうか。
>

了解しました。

>GetFromServer メソッドの利用側に、どのような記述を行わせるのであれば、
>「一苦労しない」方法になると思いますか?

魔界の仮面弁士さんの挙げた例のうち、個人的には「出力引数」が一番良いと思いました。
サーバの返すエラーの種類をアプリケーション側で細かく把握できたほうが、
融通が利くためです。

その際、以下のように、ErrorDefinitionクラスを作成し、エラーの種類を定数で持たせたいと考えました。
これを共通のライブラリに入れて、dll・アプリケーションの双方から参照させるようにしようかと。
----------
public class ErrorDefinition
{
public const int CLIENT_ARGUMENT = 0;
public const int SERVER_FILE_NOT_FOUND = 1;
:
}
----------

そうすれば、ビルドしたときにライブラリの定数名が変更されれば、
アプリケーション(またはdll)のエラー定数の参照箇所でビルドエラーが起きます。
つまり「dllの変更時にthrowされた例外を取りこぼす」等のうっかりミスが減りそうだと考えます。

> それは例外に限らず、どのような返し方であっても一緒では無いでしょうか。

dllとアプリケーション側でthrowする例外のクラスに違いがあるとき、ビルドエラーが起きれば、
即、問題箇所がわかるので助かりますよね。

もちろんdll作成者とdll利用者の意識合わせがしっかりして行われていれば対応可能です。
ですが、それは運用面の問題ですので…。
人はミスを起こしやすく、人による問題はコード面で防止されるようにしたいと考えています。
大量のコードがあった場合、チェック抜けは起こりがちだと思うので。

また、上記のようなErrorDefinitionクラスのような方法を利用すれば、
その問題が減ると考えました。

魔界の仮面弁士さんはどのような例外処理を行っていますか?

>例外だと把握しきれなくて、ILLEGAL_ARG などのint 型(を戻り値や出力引数)で
>返す場合には把握することができる…という事にはならないかと。

上記のエラー定数による手段で対応したいと思います。

>「例外の種類を変える」とは、どういう意味でしょうか?

以下の意味でした。
・MyServerException の例外が、サーバーエラー以外に対しても throw されるようになった。
・MyServerException を廃止して、別の例外が throw されるように実装し直した。

その他のご指摘については、サンプルだから意図が伝わればいいかと
詳細部分の正確さは省いて書いてしまったことが原因です。
深い意図はありませんでした。
内容の未熟さから、お目汚しいたしましたことをお詫び申し上げます。

引用返信 編集キー/
■41390 / inTopicNo.16)  Re[8]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simano (15回)-(2009/09/18(Fri) 19:26:06)
No41389 (simano さん) に返信

自己レスです。
というか、ErrorDefinitionクラスにおいて、新規に定数が増えたら同じことですね。

うーん…。
引用返信 編集キー/
■41403 / inTopicNo.17)  Re[8]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ 魔界の仮面弁士 (1303回)-(2009/09/18(Fri) 22:09:16)
2009/09/19(Sat) 10:59:13 編集(投稿者)
コードを投稿する場合は、[投稿モード]を通常モードから図表モードに切り替えてください。
(本文を記述する欄のすぐ上に、投稿モードのラジオボタンがあります)


■No41389 (simano さん) に返信
>> // エラー通知イベントでの捕捉
>> obj.OnError += delegate { MessageBox.Show("失敗"); }; // エラー理由はイベント引数で
>> MyData result = obj.GetFromServer(0);
先のこのパターンは、他と少し毛色が違いますが、非同期的に通知させる事を意図したものです。

BackgroundWorker の RunWorkerCompleted イベントや、
ADODB.Connection の InfoMessage イベントのように、
イベント引数で例外情報を伝えるという状況を想定しています。

# ……もはや参照渡し/値渡しとは関係無い話になってしまいましたが。(^^;


> 魔界の仮面弁士さんの挙げた例のうち、個人的には「出力引数」が一番良いと思いました。
> サーバの返すエラーの種類をアプリケーション側で細かく把握できたほうが、
> 融通が利くためです。
クラスの利用側にエラー内容を細かく把握させること自体は、出力引数以外の手法でも
実装することができると思いますよ(そもそも、例外処理はその一例でしょうし)。

たとえば戻り値で実装するなら、RunWorkerCompletedEventArgs クラスのプロパティのように、
詳細なエラー情報をデータと共に併せ持つといった方法もあるかと思います。
 result = obj.GetFromServer(0);
 if(result.Error == null) {
  // OK 処理
  MessageBox.Show(result.Result.ToString());
 } else {
  // NG 処理
  if(result.Error is MyServerException) {
   Recovery((MyServerException)result.Error);
  } else {
   throw new SimanoAppException("GetFromServer が予期せぬエラーを返しました。", result.Error);
  }
 }


> 個人的には「出力引数」が一番良いと思いました。
出力引数を使うにしても、その実装パターンは幾つか考えられるかと思います。
 resultStatus = obj.Foo(inputData, out outputData);
 outputData   = obj.Foo(inputData, out resultStatus);
 resultStatus = obj.Foo(ref inputOutputData);

上記はいずれも、「結果データ」と「エラーの有無」を戻り値と引数とで受け取るようになっています。
得られる結果だけみれば、同じ処理目的のために使えますが、それぞれが指し示すメソッドの「意図」は
微妙に異なるため、それぞれの実装を単純に同列に扱うわけにもいきませんよね。


> その際、以下のように、ErrorDefinitionクラスを作成し、エラーの種類を定数で持たせたいと考えました。
const int を切るかわりに、System.Net.WebExceptionStatus や
System.Net.HttpStatusCode などのように、enum にするという手もあります。

この実装方法は、エラーの種類を事前に網羅できる場合に有効かと思います。
たとえば HttpStatusCode は、HTTP/1.1 仕様書に定義されたステータスと等価ですね。
http://www.studyinghttp.net/status_code


> そうすれば、ビルドしたときにライブラリの定数名が変更されれば、
そのライブラリが DLL 参照では無く、プロジェクト参照としてソリューションに組み込まれているならば、
定数名が変更されれば、それを利用する側も機械的にリファクタリングできるかと思います。

ところで private ならばともかく、外部公開されている定数名を後から変える理由は何なのでしょうか?
その理由次第では、何か対応策があるかも知れません。

たとえば当方では、あるメンバにスペルミスが発覚したため、その綴りを変更したい…という状況が
発生した事があったのですが、そのときはスペルを直接修正するのではなく、同じ意味を持つ
正しいスペルの項目を新たに追加し、旧項目に Obsolete 属性を付与させるという手法をとりました。

こうすると、旧項目を使っている場所があれば、リコンパイル時に警告/エラーが通知されますので、
利用側で、完全に置き換え修正が完了した事を確認した上で、その次のバージョンのリリース時に
そのスペルミス項目を削除するようにしました。


> つまり「dllの変更時にthrowされた例外を取りこぼす」等のうっかりミスが減りそうだと考えます。
どの方法にしても、利点もあれば欠点もあると思います。

たとえば、if(ret == ErrorDefinition.SERVER_FILE_NOT_FOUND) の手法だと、
新たな定数コードが増えたときに、呼び出し側の取りこぼしが起こりえそうです。

すべての組みあわせに対して、else if が網羅されていれば、
最後の else にヒットして検出できるかも知れませんけれどね。


> 魔界の仮面弁士さんはどのような例外処理を行っていますか?
少々答えにくい質問ですが… MSDN のガイドラインを参考にしてはいます。
http://msdn.microsoft.com/ja-jp/library/cc433258.aspx
http://msdn.microsoft.com/ja-jp/library/dd296858.aspx
http://msdn.microsoft.com/ja-jp/library/ms229014.aspx


> >「例外の種類を変える」とは、どういう意味でしょうか?
> 以下の意味でした。
> ・MyServerException の例外が、サーバーエラー以外に対しても throw されるようになった。
だとしたら、それは設計ミスだと思いますよ。
例外クラス名と実際の例外の内容が一致しませんし。


> ・MyServerException を廃止して、別の例外が throw されるように実装し直した。
まず、廃止という点について、
 (1)GetFromServer メソッドが MyServerException を throw しなくなった。
 (2)MyServerException クラスに、Obsolete 属性を付与した。
 (3)DLL 内から、MyServerException クラス自体の定義を削除した。
といった 3 つのパターンを想像してみました。

(1) については、呼び出し元の catch 句が埋め殺しになるだけであって、
通常処理系に対しては、影響は少ないような気がします。
(通常処理から外れるがゆえの「例外」なわけですし)

(2) についてはコンパイル段階で発覚しますから、該当箇所の列挙は難しくないでしょう。

(3) に至ってはそもそもコンパイルが通らなくなるので、やはり発見は容易かと。


それから新たな例外の新設という点についてですが、それは確かに
既存の catch(MyServerException ex) では拾われないと思います。しかし
その新設した例外(あるいはその例外の継承元クラス)を catch していない場合、
それはさらにその呼び出し元に伝わっていくわけですよね。ならばテスト段階で
StackTrace を追跡すれば、問題箇所を検出することもできるのでは無いかと。

まぁ、例外を意図的に握りつぶしている箇所の再精査は必要ですが、
そのような再精査は、仕組みが違うにしろ、他の手法(戻り値や出力引数等)でも
同じように全チェックしなければならない事柄でしょうし。

引用返信 編集キー/
■41420 / inTopicNo.18)  Re[9]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ simanoha (4回)-(2009/09/19(Sat) 17:03:33)
No41403 (魔界の仮面弁士 さん) に返信
> 2009/09/19(Sat) 10:59:13 編集(投稿者)

> BackgroundWorker の RunWorkerCompleted イベントや、
> ADODB.Connection の InfoMessage イベントのように、
> イベント引数で例外情報を伝えるという状況を想定しています。

イベントで例外を投げるという方法もあるんですね。

>たとえば戻り値で実装するなら、RunWorkerCompletedEventArgs クラスのプロパティのように、
>詳細なエラー情報をデータと共に併せ持つといった方法もあるかと思います。

確かに、その方法なら"out"や"ref"の記述もないですし、使いやすそうです。

>そのライブラリが DLL 参照では無く、プロジェクト参照としてソリューションに組み込まれているならば、
>定数名が変更されれば、それを利用する側も機械的にリファクタリングできるかと思います。

そんな便利な方法があるんですね。調べてみます。

>ところで private ならばともかく、外部公開されている定数名を後から変える理由は何なのでしょうか?

携わるプロジェクト規模が小さいので、仕様が変わりやすく、
定数名にいたっても同様の状況です。
チェック環境(人も時間も…)も整っていないので、
コード面で予防効果が高い方法を選択したいと思った次第です。

その他、おかげさまで、一つ一つの現実的な実現方法が見えてきました。
経験者の方からの貴重なご意見、どうもありがとうございました。
解決済み
引用返信 編集キー/
■41425 / inTopicNo.19)  Re[10]: C#における値渡しと参照渡しのコーディングについて
□投稿者/ 渋木宏明(ひどり) (1210回)-(2009/09/19(Sat) 18:12:48)
渋木宏明(ひどり) さんの Web サイト
2009/09/19(Sat) 18:52:50 編集(投稿者)

>>BackgroundWorker の RunWorkerCompleted イベントや、
>>ADODB.Connection の InfoMessage イベントのように、
>>イベント引数で例外情報を伝えるという状況を想定しています。
>
> イベントで例外を投げるという方法もあるんですね。

この場合は、「スレッドを跨ぐのでやむを得ない(=ワーカースレッドで発生した例外をプライマリスレッドに直接伝達する方法が無い&そんなことされても迷惑だ)から」という事情があるからです。

「エラーの発生を伝える手法の一つとして、イベントを使用するのが一般に正しい設計である」というわけではありません。

> >たとえば戻り値で実装するなら、RunWorkerCompletedEventArgs クラスのプロパティのように、
> >詳細なエラー情報をデータと共に併せ持つといった方法もあるかと思います。
>
> 確かに、その方法なら"out"や"ref"の記述もないですし、使いやすそうです。

あまりよい設計とは思えません。

メソッドの戻り値にエラー情報まで含めると、メソッド本来の責務がぼやけてしまいます。
メソッドの責務の性質上「そうする必要がある」のでない限り、避けるべきと思います。

> >ところで private ならばともかく、外部公開されている定数名を後から変える理由は何なのでしょうか?
>
> 携わるプロジェクト規模が小さいので、仕様が変わりやすく、
> 定数名にいたっても同様の状況です。
> チェック環境(人も時間も…)も整っていないので、
> コード面で予防効果が高い方法を選択したいと思った次第です。

そういった問題は、ソースコード管理ツールと IDE のリファクタリング機能の利用で十分対応できると思いますよ。

# Java で開発したことがあるなら、Eclips や SVN, git なんかを使った経験ありそうな気もしますが。

引用返信 編集キー/
■41433 / inTopicNo.20)  Re[10]: C#における値渡しと参照渡しのコーディングについて
 
□投稿者/ 魔界の仮面弁士 (1304回)-(2009/09/19(Sat) 19:25:37)
No41420 (simanoha さん) に返信
>>BackgroundWorker の RunWorkerCompleted イベントや、
>>ADODB.Connection の InfoMessage イベントのように、
>>イベント引数で例外情報を伝えるという状況を想定しています。
> イベントで例外を投げるという方法もあるんですね。
あとは、DataGridView の DataError イベントなどもそうかな。。。

ただ、エラーをイベントで通知するのは、かなり特殊なパターンです。
今回の実装で使う必要は無いと思います。


>> たとえば戻り値で実装するなら、RunWorkerCompletedEventArgs クラスのプロパティのように、
>> 詳細なエラー情報をデータと共に併せ持つといった方法もあるかと思います。
> 確かに、その方法なら"out"や"ref"の記述もないですし、使いやすそうです。
# あれ。肯定されてしまったのかな? (^^;

使いやすいかどうかの判断はお任せしますが、あまり一般的なパターンでは無いと思いますよ。

「エラー情報も含め、それが取得すべき情報の一部である場合」には使えるかも知れませんが
あくまで情報の取得がメインであり、エラー発生は特殊なパターンであるならば、
素直に Exception を throw した方が、処理としては自然な気がします。

# "エラー" という言葉にどこまでの範囲を含めるのかにもよりますが。


>> ところで private ならばともかく、外部公開されている定数名を後から変える理由は何なのでしょうか?
> 携わるプロジェクト規模が小さいので、仕様が変わりやすく、
世の中には、規模が大きいのに、時間も人でも少なくて、仕様を確定しきれ(以下自粛
引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -