匿名メソッドで再帰を実装する時

通常匿名メソッドを実装する時は、宣言と代入が同じになるパターンが多い。

例えば、下記のコードのように。

public void 匿名メソッドで再帰()
{
Func<int, bool> カウントダウン = (x) =>
{
if (x == 0)
{
return true;
}
Console.WriteLine(x);
x--;
return カウントダウン(x);
};
カウントダウン(40);
}

しかし、上記のコードだと、

未割り当てのローカル変数 'カウントダウン' が使用されました。

と言われてコンパイルエラーになる。

では、どうしたら良いかというと、先に匿名メソッドを代入する変数を宣言しておけばよい。

下記のコードならコンパイルエラーにならないで正しく動作する。

public void 匿名メソッドで再帰()
{
Func<int, bool> カウントダウン = null;
カウントダウン = (x) =>
{
if (x == 0)
{
return true;
}
Console.WriteLine(x);
x--;
return カウントダウン(x);
};
カウントダウン(40);
}
Share

パズル:nまでの整数を二つの組に分けて合計した結果が同じだった2番目と3番目を出力する その2

問題:1からnまでの整数を二つの組に分けて合計した結果が同じだった2番目と3番目を出力する。

パズル:nまでの整数を二つの組に分けて合計した結果が同じだった2番目と3番目を出力する – NAL-6295の舌先三寸

出力された値を眺めていたら、どうも法則があることが分かった。

3は例外として、それ以降の値については、常に前回値×6-(前々回値-2)となるっぽい。

多分、知っている人には当たり前の事なんだろうなと思いつつ。

数式にするとこんな感じ?勉強していないので表記が間違っているかもしれませんが・・・

N¥tiny nを任意の整数とし

N¥tiny 0¥large =-1

N¥tiny 1¥large =0

としたとき、

N¥tiny n+1¥large=N¥tiny n ¥large 6-(N¥tiny n-1 ¥large -2)

となる。

初めて、texというのをつかったけど、こんなんで良いのかな?

間違っていたら教えてください。

でも、なんで成り立つのか分からないな。

03
20 3*6-(0-2)
119 20*6-(3-2)
696 119*6-(20-2)
4059 696*6-(119-2)
23660 4059*6-(696-2)
137903 23660*6-(4059-2)
803760 137903*6-(23660-2)

というわけで、プログラムに反映してみた。

ついでなので、仕様もintの範囲で該当する数字全てにしてみた。

public void nまでの整数を二つの組に分けて合計した結果が同じだった数字リスト()
{
Console.WriteLine(3);
int 前回の値 = 3;
int 前々回の値 = 0;
while ((long)前回の値 * (long)6 <= int.MaxValue)
{
int 今回の値 = 前回の値 * 6 - (前々回の値 - 2);
Console.WriteLine(今回の値);
前々回の値 = 前回の値;
前回の値 = 今回の値;
}
}

前のコードだとぜんぜん終わらなかったのに、一瞬で終わるようになった。

当たり前か・・・。

Share
カテゴリー: C#

パズル:nまでの整数を二つの組に分けて合計した結果が同じだった2番目と3番目を出力する

問題:1からnまでの整数を二つの組に分けて合計した結果が同じだった2番目と3番目を出力する。

public void nまでの整数を二つの組に分けて合計した結果が同じだった2番目と3番目()
{
//Enumerable.Select().Sum()は使わない方向で
Func<int, int, int> xからyまでの計 = (int x, int y) => ((x + y) * ((y - x + 1) / 2)) + (((y + x) / 2) * ((y - x + 1) % 2));
Func<int, bool> nまでの数字を二つの組に分けて合計した結果が同じ = (int n) => Enumerable.Range(n / 2, n - (n / 2))
.Where(分ける数字 => xからyまでの計(1, 分ける数字) ==
xからyまでの計(分ける数字 + 1, n))
.Select(x => x).Count() > 0;
int[] 該当するnのリスト = Enumerable.Range(2, int.MaxValue - 2).Where(n => nまでの数字を二つの組に分けて合計した結果が同じ(n))
.Select(n => n)
.Take(3).ToArray();
Console.WriteLine("no2:{0},no3:{1}", 該当するnのリスト[1], 該当するnのリスト[2]);
}
Share

超今更な話だけどGenericの型パラメータの制約は便利だ

最近、Genericを利用したクラスを作成する機会があった。

もともと、以下のような感じのGenericを利用していないクラスがあったのをGenericに対応した。

public abstract class ほにゃAbstract
{
public ほにゃAbstract()
{
}
public abstract DataTable TableData
{get;}
}
public class ふが :ほにゃabstract
{
private 型のついたテーブル型 _data = new 型のついたテーブル型();
public override DataTable TableData
{
get
{
return _data;
}
}
}

というクラスがあったとして、Dataプロパティを利用するときは、常にDataTable型なので、型付DataSetで定義したテーブルを返す時は

ふが ふがインスタンス = インスタンス作成();
型のついたテーブル型 データ = (型のついたテーブル型)ふがインスタンス.TableData;

といった感じにいちいちキャストする必要があったが

public abstract class ほにゃAbstract<T> where T:DataTable,new()
{
public abstract ほにゃAbstract()
{
}
private T _data = new T();
public abstract T TableData
{
get
{
return _data;
}
}
}
public class ふが :ほにゃabstract<型のついたテーブル型>
{
}

とすることで、

ふが ふがインスタンス = インスタンス作成();
型のついたテーブル型 データ = ふがインスタンス.TableData;

とキャストしなくて良くなった。

ここで、重要だと思ったのが、

public abstract class ほにゃAbstract<T> where T:DataTable,new()

のwhere以降の部分で、Tに設定できる型の条件を指定できる。

T:DataTable

で、DataTableを基本型に持つクラスのみ指定可能なので、DataTableを継承した型である事という条件がクリアできる。

次に

new()

とつけることで、

private T _data = new T();

といったように抽象クラス側でインスタンスの生成ができるようになったので、いちいちサブクラス側でメンバを設定する必要が無くなった。

今更だし、凄い簡単な話なんだけど、便利だと思った。

そういえば、MSDNライブラリの型パラメータの制約ページの構文を説明している箇所で

http://msdn.microsoft.com/ja-jp/library/d5x73970.aspx

where T:struct

where T:class

のままで良いのに、

where T:構造体

where T:クラス

になっていた。

翻訳されすぎ。

Share

拡張メソッドを使ってみる

拡張メソッドを使ってみたかったので、ちょっと遊んでみます。

以前、LINQを利用して、読みやすいコードを書くサンプルとして以下のようなコードを提示していました。

class 在庫計
{
public int 数量計 { get; set; }
public int 金額計 { get; set; }
}
public class サンプル
{
public void サンプルメソッド(在庫[] 在庫リスト)
{
Func<在庫, bool> 集計対象 = 在庫情報 =>
{
if (在庫情報.数量 <= 10)
{
return false;
}
if (在庫情報.数量 % 1000 != 0)
{
return false;
}
return true;
};
var 在庫計情報 = 集計(在庫リスト,集計対象);
System.Windows.Forms.MessageBox.Show(string.Format("数量計 = {0},金額計 = {1} ", 在庫計情報.数量計, 在庫計情報.金額計));
}
private 在庫計 集計(在庫[] 在庫リスト,Func<在庫,bool> 集計対象)
{
在庫計 在庫計情報 = new 在庫計();
在庫計情報.数量計 = 在庫リスト
.Where(集計対象)
.Sum(x => x.数量);
在庫計情報.金額計 = 在庫リスト
.Where(集計対象)
.Sum(x => x.金額);
return 在庫計情報;
}
}

集計メソッドで在庫リストにある数量と金額の合計を作成するわけですが、集計メソッドに在庫リストと抽出条件を引数で渡しているだけなので、特に在庫リストに結びついた処理であるといった情報はありません。

そこで拡張メソッドを利用したサンプルを書いてみます。

class 在庫計
{
public int 数量計 { get; set; }
public int 金額計 { get; set; }
}
class サンプル
{
public void サンプルメソッド(在庫[] 在庫リスト)
{
Func<在庫, Boolean> 抽出条件 = 在庫情報 =>
{
if (在庫情報.数量 <= 10)
{
return false;
}
if (在庫情報.数量 % 1000 != 0)
{
return false;
}
return true;
};
var 在庫計情報 = 在庫リスト.集計(抽出条件);
System.Windows.Forms.MessageBox.Show(string.Format("数量計 = {0},金額計 = {1} ", 在庫計情報.数量計, 在庫計情報.金額計));
}
}
static class 在庫処理拡張サンプル
{
public static 在庫計 集計(this 在庫[] 在庫リスト, Func<在庫, Boolean> 抽出条件)
{
在庫計 在庫計情報 = new 在庫計();
var result = from 在庫情報 in 在庫リスト
where 抽出条件(在庫情報)
select 在庫情報;
在庫計情報.数量計 = result.Sum(x => x.数量);
在庫計情報.金額計 = result.Sum(x => x.金額);
return 在庫計情報;
}
}

この例の場合、在庫リスト.集計とコーディングできるため、「在庫リストの集計」と読むことができて、より文脈に沿ったコードを書くことができるようになりました。

まぁ、在庫リストなんて在庫の配列があるかといえば、だいたいにおいて在庫テーブルにアクセスするクラスがあって、そこに集計メソッドがあれば済む話なので拡張メソッドを利用する事は無いし、あまり多用するものでもないのですが、今回の例みたいに

「特定の配列に処理を追加したい。」

なんてときには便利かもしれません。

Share

LINQのGroupJoinを使ってみる

LINQに普通のJoinのほかにGroupJoinという機能がありますので、ちょっと使って遊んでみました。

通常のJoinの場合

var 商品リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター" },
new { 商品番号 = "B", 商品名 = "安全ハサミ" },
new { 商品番号 = "C", 商品名 = "ラジオペンチ"}};
var 在庫リスト = new[] { new { 商品番号 = "A", 数量 = 30 ,LotNo = 1} ,
new { 商品番号 = "A", 数量 = 40 ,LotNo = 2} ,
new { 商品番号 = "A", 数量 = 50 ,LotNo = 3} ,
new { 商品番号 = "A", 数量 = 60 ,LotNo = 4} ,
new { 商品番号 = "A", 数量 = 70 ,LotNo = 5} ,
new { 商品番号 = "A", 数量 = 90 ,LotNo = 6} ,
new { 商品番号 = "A", 数量 = 80 ,LotNo = 7} ,
new {商品番号 = "C",数量 = 69,LotNo = 8}};
var 商品別LOTNO別リスト = from 商品 in 商品リスト
join 在庫 in 在庫リスト
on 商品.商品番号 equals 在庫.商品番号
select new
{
商品番号 = 商品.商品番号,
商品名 = 商品.商品名,
LOTNO = 在庫.LOTNO,
数量 = 在庫.数量
};

というように二つの匿名型の配列を商品番号で結合したときの結果は

var 商品別LOTNO別リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 30 ,LotNo = 1} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 40 ,LotNo = 2} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 50 ,LotNo = 3} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 60 ,LotNo = 4} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 70 ,LotNo = 5} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 90 ,LotNo = 6} ,
new { 商品番号 = "A", 商品名 = "鼻毛カッター", 数量 = 80 ,LotNo = 7} ,
new {商品番号 = "C",, 商品名 = "ラジオペンチ", 数量 = 69,LotNo = 8}};

というように、SQLでINNER JOINをする時と同じような結果になります。

つまり、商品リストの商品番号に対して在庫リスト中に同じ商品番号があれば、それだけオブジェクトが作られます。

もちろん、対象の商品番号が無かったオブジェクトは作られません。

大して、

var 商品リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター" },
new { 商品番号 = "B", 商品名 = "安全ハサミ" },
new { 商品番号 = "C", 商品名 = "ラジオペンチ"}};
var 在庫リスト = new[] { new { 商品番号 = "A", 数量 = 30 ,LotNo = 1} ,
new { 商品番号 = "A", 数量 = 40 ,LotNo = 2} ,
new { 商品番号 = "A", 数量 = 50 ,LotNo = 3} ,
new { 商品番号 = "A", 数量 = 60 ,LotNo = 4} ,
new { 商品番号 = "A", 数量 = 70 ,LotNo = 5} ,
new { 商品番号 = "A", 数量 = 90 ,LotNo = 6} ,
new { 商品番号 = "A", 数量 = 80 ,LotNo = 7} ,
new {商品番号 = "C",数量 = 69,LotNo = 8}};
var 商品別在庫リスト = from 商品 in 商品リスト
join 在庫 in 在庫リスト
on 商品.商品番号 equals 在庫.商品番号 into 商品別在庫
select new
{   商品番号 = 商品.商品番号,
商品名 = 商品.商品名,
数量 = 商品別在庫.Sum(x => x.数量)
};
foreach (var 商品別在庫 in 商品別在庫リスト)
{
Console.WriteLine(string.Format("商品番号:{0} 商品名:{1} 在庫数量:{2}",
商品別在庫.商品番号,
商品別在庫.商品名,
商品別在庫.数量));
}

Join … Intoという構文で上記のようにGroupJoinをすると、

var 商品別在庫リスト = new[] { new { 商品番号 = "A", 商品名 = "鼻毛カッター" ,数量 = 420},
new { 商品番号 = "B", 商品名 = "安全ハサミ" ,数量 = 0},
new { 商品番号 = "C", 商品名 = "ラジオペンチ",数量 = 69}};

というように、商品リストの商品番号と同じ商品番号の在庫は集約されて結合されますので、商品番号単位に在庫リストが作成されることになり、商品番号別の数量の合計を出力する事が可能です。

また、在庫リストに対象の商品番号が無いオブジェクトも作られます。

Share

読みやすいコードのためにLINQを利用する。

最初に断っておきたいこと

C#3.0の話です。
LINQやラムダ式自体の説明はしていません。
簡単なサンプルにするため仕様がシンプルで効果を感じられないかもしれませんが、
もしもっと複雑な仕様だったら等、想像してみてください。

まず最初に、下記のサンプルを見てください。

簡単な仕様として、

受け取った在庫リストの集計対象のものだけ集計したい。
集計対象とは、数量が10より大きくて、数量を1000で割った時の余りが0のものの事である。

を実現します。

class サンプル1
{
private class 在庫計
{
public int 数量計 { get; set; }
public int 金額計 { get; set; }
}
public void サンプルメソッド(在庫[] 在庫リスト)
{
在庫計 在庫計情報 = new 在庫計();
foreach (在庫 在庫情報 in 在庫リスト)
{
if (在庫情報.数量 > 10)
{
if ((在庫情報.数量 % 1000 == 0))
{
在庫計情報.数量計 += 在庫情報.数量;
在庫計情報.金額計 += 在庫情報.金額;
}
}
}
}
}

この例の場合、たしかに仕様を実現していますが、if文が実現している仕様についてコード上から読み取ることができません。

そこで、ラムダ式を利用し、集計対象の時にtrueを返す匿名メソッドを作成して実装してみます。

class サンプル2
{
private class 在庫計
{
public int 数量計 { get; set; }
public int 金額計 { get; set; }
}
public void サンプルメソッド(在庫[] 在庫リスト)
{
Func<在庫, Boolean> 集計対象 = 在庫情報 =>
{
if (在庫情報.数量 <= 10)
{
return false;
}
if (在庫情報.数量 % 1000 != 0)
{
return false;
}
return true;
};
在庫計 在庫計情報 = new 在庫計();
foreach (在庫 在庫情報 in 在庫リスト)
{
if (集計対象(在庫情報))
{
在庫計情報.数量計 += 在庫情報.数量;
在庫計情報.金額計 += 在庫情報.金額;
}
}
}

その結果、集計対象の在庫のみ集計するという部分と、集計対象である条件の詳細部分が分離されました。

ここでさらにLINQを利用して、ループの部分が実際に何をしているのかをコードに残すようにします。

public class サンプル3
{
public void サンプルメソッド(在庫[] 在庫リスト)
{
Func<在庫, bool> 集計対象 = 在庫情報 =>
{
if (在庫情報.数量 <= 10)
{
return false;
}
if (在庫情報.数量 % 1000 != 0)
{
return false;
}
return true;
};
var 在庫計情報 = new
{
数量計 = 在庫リスト
.Where(集計対象)
.Sum(x => x.数量),
金額計 = 在庫リスト
.Where(集計対象)
.Sum(x => x.金額)
};
}
}

もしくは、

public class サンプル4
{
public void サンプルメソッド(在庫[] 在庫リスト)
{
Func<在庫, bool> 抽出条件 = 在庫情報 =>
{
if (在庫情報.数量 <= 10)
{
return false;
}
if (在庫情報.数量 % 1000 != 0)
{
return false;
}
return true;
};
var 在庫計情報 = new
{
数量計 = (from 在庫情報 in 在庫リスト
where 抽出条件(在庫情報)
select 在庫情報.数量).Sum()
,
金額計 = (from 在庫情報 in 在庫リスト
where 抽出条件(在庫情報)
select 在庫情報.金額).Sum()
};
}
}

その結果、ループがなくなりましたが、在庫リストから集計対象のもののみを抽出(Where)し、集計(Sum)している事がコードに埋め込まれました。

また、在庫計情報をループで加算しないため、サンプル2までは在庫計というinner classを作成していましたが、その必要がなくなり、その場限りの匿名型で済むようになりました。

ところでサンプルコードだけを見ているとわかりませんが、集計した結果がint型の範囲を超えた時、サンプル2まで(ループで集計している)とサンプル3以降(LINQを利用している。)で実行時の挙動が違います。

サンプル2までは、例外が発生せず、マイナス値になってしまいます。

サンプル3以降は、ちゃんとOverflowExceptionが発生します。

もしサンプル2までのコードで例外が発生するようにする場合は

単純に

//抜粋
foreach (在庫 在庫情報 in 在庫リスト)
{
if (集計対象(在庫情報))
{
if (在庫計情報.金額計 + 在庫情報.金額 > int.MaxValue)
{
throw new OverflowException();
}
在庫計情報.数量計 += 在庫情報.数量;
在庫計情報.金額計 += 在庫情報.金額;
}
}

とすると、intの範囲を超えた場合

在庫計情報.金額計 + 在庫情報.金額

がマイナス値になるため

在庫計情報.金額計 + 在庫情報.金額 > int.MaxValue

の条件がtrueになることが無いため、いつまでも例外が発生しません。

そのためより値のとれる範囲の大きいlongに型変換したうえで、

//抜粋
foreach (在庫 在庫情報 in 在庫リスト)
{
if (集計対象(在庫情報))
{
if ((long)在庫計情報.金額計 + (long)在庫情報.金額 > int.MaxValue)
{
throw new OverflowException();
}
在庫計情報.数量計 += 在庫情報.数量;
在庫計情報.金額計 += 在庫情報.金額;
}
}

とするか、金額にマイナス値が無い前提であれば、

//抜粋
foreach (在庫 在庫情報 in 在庫リスト)
{
if (集計対象(在庫情報))
{
if (在庫計情報.金額計 + 在庫情報.金額 < 0)
{
throw new OverflowException();
}
在庫計情報.数量計 += 在庫情報.数量;
在庫計情報.金額計 += 在庫情報.金額;
}
}

とする必要があります。

つまり、うっかり間違った実装をしてしまうと、オーバフロー時に例外を発行させているつもりで、実は発行していないという状況になってしまいますので、そういう点でも、このくらいの集計ならLINQを利用した方が安全です。

Share

ラムダ式の=>演算子の読み方。(訂正しました)

ラムダ式の=>演算子(らむだえんざんし)をどう発音するのかわからなかったのですが、ちゃんとリファレンスに載ってました。

「goes to」と読むんですね。

ということで、

x => x + 1;

x goes to x plus one.

と読むんだそうで、どうでもいいことですが・・・。

#訂正しました。

どこでどう間違ったのか・・・。

goes on ではなく goes toが正解です。

申し訳ありません。

Share

読みやすいコードを書くためにFunc型と匿名メソッドを利用する。

最初に断っておきたいこと

C#3.0の話です。
Func型や匿名メソッド自体の説明はしていません。

まず最初に、私が考える読みやすいコードの要素について断片的に提示します。

1.処理のスコープを最小にする。

2.今、読みたい粒度のコードしか読まなくても良い。

というものがあります。

どれも、いくつかある読みやすいコードの要素の断片でしかありませんが、この1と2は矛盾を孕んでいます。

それでは、どんな矛盾を孕んでいるかサンプルを交えて説明します。

001     public class サンプル1
002     {
003         public void サンプルメソッド()
004         {
005             if (鼻毛抜きの在庫 == 0 && 鼻毛カッターの在庫 == 0)
006             {
007                 return;
008             }
009 
010             鼻毛処理();
011         }
012     }
013 

鼻毛処理のできる道具があれば、鼻毛処理ができる。

という仕様を実現しています。

しかしながら、「鼻毛処理のできる道具があれば」の詳細部分まで同じメソッド内に記述されてしまっています。

そこで、

001     public class サンプル2
002     {
003         public void サンプルメソッド()
004         {
005             if (!鼻毛処理の道具がある())
006             {
007                 return;
008             }
009 
010             鼻毛処理();
011         }
012 
013         private bool 鼻毛処理の道具がある()
014         {
015             if (鼻毛抜きの在庫 > 0)
016             {
017                 return true;
018             }
019 
020             if (鼻毛カッターの在庫 > 0)
021             {
022                 return true;
023             }
024 
025             return false;
026         }
027 
028         public void その他の処理()
029         {
030             throw new NotImplementedException();
031         }
032 
033     }
034 

これで、「2.今、読みたい粒度のコードしか読まなくても良い。」という要素が満たされた実装になりました。

しかしメソッドアウトする前は、処理のスコープがメソッド内にしかなかったのに、メソッドアウトした事で処理のスコープがクラス全体に広がってしまいました。

実装したばかりの今なら、まだサンプルメソッド内でしか使われていないとわかりますが、将来このコードを見たときに、サンプルメソッド内でしか使われていないと断定することができません。

これでは、「1.処理のスコープを最小にする」という要素が満たされなくなってしまっています。

そこで、以下のようにFunc型と匿名メソッドを利用してサンプルを修正してみます。

001     public class サンプル3
002     {
003         public void サンプルメソッド()
004         {
005             Func<bool> 鼻毛処理の道具がある =
006                 delegate
007                 {
008                     if (鼻毛抜きの在庫 > 0)
009                     {
010                         return true;
011                     }
012 
013                     if (鼻毛カッターの在庫 > 0)
014                     {
015                         return true;
016                     }
017 
018                     return false;
019                 };
020 
021             if (!鼻毛処理の道具がある())
022             {
023                 return ;
024             }
025 
026             鼻毛処理();
027         }
028 

これで、匿名メソッドという形でメソッドアウトし「2.今、読みたい粒度のコードしか読まなくても良い。」という要素を満たした上に、処理のスコープもメソッド内にとどまりますので、「1.処理のスコープを最小にする。」という要素も満たされます。

「鼻毛処理の道具があれば」という仕様の詳細まで知る必要が無いときは匿名メソッドを読み飛ばせば良いのです。

以上のように「ひとつのメソッドの中でしか利用しないが、粒度が違うためメソッドアウトする。」といったような局面において最小のスコープを維持したままメソッドアウトする道具として匿名メソッドを使う例の紹介でした。

ちなみに、サンプル3をラムダ式で書くと

001     public class サンプル3
002     {
003         public void サンプルメソッド()
004         {
005             Func<bool> 鼻毛処理の道具がある =
006                 () =>
007                 {
008                     if (鼻毛抜きの在庫 > 0)
009                     {
010                         return true;
011                     }
012 
013                     if (鼻毛カッターの在庫 > 0)
014                     {
015                         return true;
016                     }
017 
018                     return false;
019                 };
020 
021             if (!鼻毛処理の道具がある())
022             {
023                 return ;
024             }
025 
026             鼻毛処理();
027         }
028 

となります。

Share

参照型にrefキーワードがついている時とついていない時の違い

メソッドを定義するときに、refキーワードを参照型に付けたときと付けなかったときの違いは、簡単に言えば・・・

・ref付きは参照する場所を変更出来る。

・ref無しは参照する場所を変更出来ない。

・両方とも、参照先のオブジェクトが持った値を変更する事は可能。

である。

例をあげれば・・・

001         private void button1_Click(object sender, System.EventArgs e)
002         {
003             testClass a = new testClass();
004             a.VAL = "test";
005             test(ref a);
006             //"testdayo2"が表示される。
007             MessageBox.Show(a.VAL);
008             test2(a);
009             //"testdaro"が表示される。
010             MessageBox.Show(a.VAL);
011         }
012 
013         //変数そのものが渡される。
014         private void test(ref testClass val)
015         {
016             //変数の中にあるプロパティの変更は反映される。
017             val.VAL = "testdayo";
018             //参照の変更も反映される。
019             val = new testClass();
020             val.VAL = "testdayo2";
021         }
022         //変数のコピーが渡される。
023         private void test2(testClass val)
024         {
025             //変数のこぴーであっても、参照先は同じなので
026             //変数の中にあるプロパティの変更は反映される。
027             val.VAL = "testdaro";
028             //参照の変更は関数内でのみ反映される。
029             //なぜなら変数のコピーでしかないから。
030             val = new testClass();
031             val.VAL = "testdakedo";
032         }
033     }
034 
035     public class testClass
036     {
037         private string _val;
038         public string VAL
039         {
040             get
041             {
042                 return _val;
043             }
044             set
045             {
046                 _val =value;
047             }
048         }
049     }
050 

という事である。

動作を何かに例えるならば、

ref付き引数のメソッドをAさんとすると・・・

・Aさんに住所録渡したら、住所録に書いてあった住所の家に落書きされていた。

また、住所録を返して貰ったら住所録の中身が変わっていた。

ref無し引数のメソッドをBさんとすると・・・

・Bさんにアドレス帳の内容をコピーして渡したら、住所録に書いてあった住所の家に落書きされていた。

といった感じである。

Share