狠狠撸

狠狠撸Share a Scribd company logo
結果がイメージしにくいコードと
実行結果 for .net
2018年10月13日 第16回 まどべんよっかいち
可知 一輝
自己紹介
? ひとりでやってます(ふりーらんすえんじにあとか言うらしい)
? SNS Accounts
? Facebook:Kazuki.Kachi
? Twitter :@kazuki_Kachi
? Microsoft MVP for Developer Technologies(2017-2019)
今日は、ネタがなかった参加者が意外にかぶって
いたので8月に話した微妙にわかりにくいコードの
実行結果の解説をしてみる時間です。
当時書いた前提はこちら
? 正解しても商品とかはありません。
? これどうなるんでしたっけ?的なコードを集めた(つもり)
? 間違えても誰にも怒られたりはしませn。
? 疑問に思った事は聞いてください(誰かが答えてくれるハズ)
? 実行環境は、.Net core 2.1(Windows10 1803)です。
とはいえ、参加してない方もいらっしゃいますし、参加
していたとしても覚えていない方が大半だと思います。
当時使用したコードと実行結果をもとに進めていきます。
※コードは一部省略しています。
※using ブロックはすべて省略
結果はどうなる?(その1)
static void AddVisualBasic(List<string> target) => target?.Add("VisualBasic");
static void Main(string[] args)
{
:
var languages = new List<string>() { "C#" };
AddVisualBasic(languages);
WriteLine(languages);
}
結果はこうなる(その1)
System.Collections.Generic.List`1[System.String]
何故こうなる(その1)
object classのToString()Methodの実装が、
return GetType().ToString();
となっており、List<T>classはToString()Methodをoverrideしていないため。
Javaなんかだと、内部の値を表示してくれたりするけど、C#はそこまでしてくれない。
結果はどうなる?(その2)
static void AddVisualBasic(List<string> target) => target?.Add("VisualBasic");
static void Main(string[] args)
{
:
var languages = new List<string>() { "C#" };
AddVisualBasic(languages);
languages.ForEach(WriteLine);
}
結果はこうなる(その2)
C#
VisualBasic
何故こうなる(その2)
C#では、キーワードを指定せずMethodに引数を与えた場合、stackのコピーが渡される。
classの場合、stackにinstanceではなくinstanceの場所(Address)が格納されているため、
instanceに対する変更は呼び出し元に伝搬される。
結果はどうなる?(その3)
{
:
void changeList(List<string> target)
{
target = new List<string> { "VisualBasic" };
};
var languages = new List<string>() { "C#" };
changeList(languages);
languages.ForEach(WriteLine);
}
結果はこうなる(その3)
C#
何故こうなる(その3)
C#では、キーワードを指定せずMethodに引数を与えた場合、stackのコピーが渡される。
この場合のtargetはあくまでlanguagesのAddressが格納されてはいるものの、
languegesとは別なのでtargetを書き換えても呼び出し元には伝搬しない。
結果はどうなる?(その4)
interface IValue<T> { T Value { get; set; } }
struct ValueStruct<T> : IValue<T>
{
public T Value { get; set; }
public override string ToString()
=> $"Value is {Value?.ToString() ?? "null"}!";
}
{
:
void changeValue<T>(ValueStruct<T> target, T value) => target.Value = value;
var v = new ValueStruct<int>() { Value = 5 };
changeValue(v, 100);
WriteLine(v);
}
結果はこうなる(その4)
Value is 5!
何故こうなる(その4)
C#では、キーワードを指定せずMethodに引数を与えた場合、stackのコピーが渡される。
structの場合、stackにinstanceが格納されているため、
instanceに対する変更はあくまでもコピーに対する変更のため、呼び出し元に伝搬されない。
結果はどうなる?(その5)
interface IValue<T> { T Value { get; set; } }
struct ValueStruct<T> : IValue<T>
{
public T Value { get; set; }
public override string ToString()
=> $"Value is {Value?.ToString() ?? "null"}!";
}
{
:
void changeValue<T>(IValue<T> target, T value) => target.Value = value;
var v = new ValueStruct<int>() { Value = 5 };
changeValue(v, 100);
WriteLine(v);
}
結果はこうなる(その5)
Value is 5!
何故こうなる(その5)
C#では、キーワードを指定せずMethodに引数を与えた場合、stackのコピーが渡される。
interfaseの場合はclass同様、stackにinstanceのaddressが格納されているが、
作成したinstance(宣言した変数の型)はstructのため、stackがコピーされた後、参照が渡されるため、
結果instanceに対する変更コピーに対する変更になり、呼び出し元に伝搬されない。
余談ですが、先のコードの、
var v = new ValueStruct<int>() { Value = 5 }; を
IValue v = new ValueStruct<int>() { Value = 5 }; に変更すると、
vは参照型の変数になるため変更結果が伝搬する
結果はどうなる?(その6)
{
:
var i = 0;
void output() => WriteLine(i);
i = 100;
output();
}
結果はこうなる(その6)
100
何故こうなる(その6)
Local関数(lambda式含む)は、実際にはprivateなMethodとして生成され、実行される。
この場合、output()はprivate staticなMethodとして展開され、
int iはoutputから参照できるスコープに移動される(VisualStudioを使用している限り意識することはない)
そのため、output()を実行する前にi の値を変更したら、変更後の値が出力される
(展開イメージ(実際の展開結果ではありません))
static int i;
static void Output() => WriteLine(i);
{
i = 100;
Output();
}
結果はどうなる?(その7)
{
:
var i = 0;
Action output(int value) => () => WriteLine(value);
var act = output(i);
i = 100;
act();
}
結果はこうなる(その7)
0
何故こうなる(その7)
このケースでは、int i を int value に渡した時点のi と value の関係は、
value は output()を呼び出した時点のi の値のコピーであるため、その後iをいくら変更しても
Valueには伝搬しない。
結果はどうなる?(その8)
{
:
var i = 0;
Action act = () => WriteLine(i);
while(10 > ++i) act += () => WriteLine(i);
act();
}
結果はこうなる(その8)
10
10
10
10
10
10
10
10
10
10
何故こうなる(その8)
その6に書いたことと同じ理由。
結果はどうなる?(その9)
{
:
Action act = null;
foreach(var i in Enumerable.Range(0, 10)) act += () => WriteLine(i);
act();
}
結果はこうなる(その9)
0
1
2
3
4
5
6
7
8
9
何故こうなる(その9)
iは、ループのたびに生成され、解放されている。
すなわちWriteLineが最初に受取ったiと次に受取ったiは別のものとしてキャプチャされる。
(foreachの展開イメージ)
using(var ie = Enumerable.Range(0, 10).GetEnumerator())
{
while(ie.MoveNext())
{
var i = ie.Current;
act += () => WriteLine(i);
}
}
結果はどうなる?(その10)
Shared Sub Q10()
WriteLine(1 / 2)
End Sub
結果はこうなる(その10)
0.5
何故こうなる(その10)
VisualBasicのoperator /(Integer,Integer)の戻り値の方はDouble。
…察してください。
ちなみにVBでInteger型の商を得る演算子は、(Integer,Integer)
ここで紹介できたコードは、実行結果がイメージと違う
かもしれないコードのほんの一部です。
しかも、該当箇所を抜粋しています。
書きたくなる場面もあるかもしれませんが、
未来の自分のためにもなるべく書かないことを押すsumし
ます。
ありがとうございました。

More Related Content

Unimaginable code & commentary