佐々木屋

技術的なことから趣味まで色々書きます

遅延評価と先行評価

式や配列などの走査で必要な要素を必要な時だけ評価するような挙動を遅延評価と言います。イテレーターは遅延評価される処理の典型です。LINQなどでも遅延評価があります。

例えば、イテレーターでも出てきた以下の配列処理を考えます。

private List<string> Naiyo() {
    List<string> res = new List<string>();

    res.Add("hoge");
    Console.WriteLine("hoge終了");

    res.Add("piyo");
    Console.WriteLine("piyo終了");

    res.Add("fuga");
    Console.WriteLine("fuga終了");
    
    return res;
}

foreach (var n in Naiyo()) {
    Console.WriteLine(n);
}
Private Function Naiyo() As List(Of String)
    Dim res As New List(Of String)

    res.Add("hoge")
    Console.WriteLine("hoge終了")

    res.Add("piyo")
    Console.WriteLine("piyo終了")

    res.Add("fuga")
    Console.WriteLine("fuga終了")

    Return res
End Function

For Each n As String In Naiyo()
    Console.WriteLine(n)
Next

これを実行すると、

hoge終了
piyo終了
fuga終了
hoge
piyo
fuga

つまり、Naiyoメソッドが先に全て評価(先行評価)され、その後に配列が処理されているのが分かると思います。

これをイテレーターを使って遅延評価にするとどうなるでしょうか。

private IEnumerable<string> INaiyo() {
    yield return "hoge";
    Console.WriteLine("hoge終了");

    yield return "piyo";
    Console.WriteLine("piyo終了");

    yield return "fuga";
    Console.WriteLine("fuga終了");
}
Private Iterator Function INaiyo() As IEnumerable(Of String)
    Yield "hoge"
    Console.WriteLine("hoge終了")

    Yield "piyo"
    Console.WriteLine("piyo終了")

    Yield "fuga"
    Console.WriteLine("fuga終了")
End Function
hoge
hoge終了
piyo
piyo終了
fuga
fuga終了

どうでしょうか。結果が変わりましたね。

INaiyoメソッドはyield returnまで評価され、配列処理が一つ終わるとINaiyoメソッドの続きが実行されます。そしてまた、yield returnまで実行される、という感じで順番に評価されていることが分かると思います。

まとめると、先行評価である通常のメソッドはメソッド全体を評価して結果を返しますが、遅延評価のイテレーターの場合はyieldまで到達したら処理を中断し呼び出し元の処理に戻します。つまり、遅延評価というのは必要な時が来るまで保留しておき、必要な分しか評価しない、ということになります。

遅延評価の話はLINQを利用した際も同様となります。

型変換は何を使う?(その②)

型変換は何を使う?(その①)の続きです。
sasaki816.hatenablog.com

数値型で有意なParse、TryParse

数値型はnullは許容しませんので、関数化できるならTryParse一択だと思います。但し、string型からの変換のみ利用できることと、TryParseはout引数を持ちますので、変数宣言がひとつ必要となります。

public decimal NL(string value) {
    decimal res;
    if (decimal.TryParse(value, out res)) {
        return res;
    }
    else {
        return 0;
    }
}
Public Function NL(ByVal value As String) As Decimal
    Dim res As Decimal
    If Decimal.TryParse(value, res) Then
        Return res
    Else
        Return 0
    End If
End Function

nullの時(エラーの時)何を返すのか?がミソですね。


なお、null許容型であればC#VB.NETで少し処理が異なります。VB.NETの場合はIf文による条件分岐かnull条件演算子が有用ですが、C#の場合はnull合体演算子が使用できます。

nullを許容するConvertクラス

Convertクラスはnullが来ても例外が発生しません。例えばint32へのキャストの場合、nullの時は0を返します。よって、評価後の振る舞いそして意図しない結果を返してしまう恐れがありますので注意が必要です。

string a = "123";
string b = null;
Console.WriteLine(Convert.ToInt32(a));
Console.WriteLine(Convert.ToInt32(b));
Dim a As String = "123"
Dim b As String = Nothing
Console.WriteLine(Convert.ToInt32(a))
Console.WriteLine(Convert.ToInt32(b))
123
0

そもそもParseでもDirectCastでも、構造化例外をしっかり立てればnullを許容する必要はないので、Convertクラスを利用しなければならないような場面は少ないです。逆にnullでも0を返してしまいますので、局所的な部分でなければ正直使いにくいクラスですね。

文字列変換専用のToString

書式設定付きで変換するならToStringが一番適しています。但し、nullは許容していませんので、必要に応じて構造化例外処理が必要となります。

int a = 12345;
DateTime b = new DateTime(2019, 4, 1);
Console.WriteLine(a.ToString("#,0"));
Console.WriteLine(b.ToString("yyyyMMdd"));
Dim a As Integer = 12345
Dim b As DateTime = New Date(2019, 4, 1)
Console.WriteLine(a.ToString("#,0"))
Console.WriteLine(b.ToString("yyyyMMdd"))
12,345
20190401

抽象クラスと抽象メソッド

クラスを継承した時に仮想メソッドであれば派生先でオーバーライドできることは説明しました。この時、派生先で最初からオーバーライドありきで設計されたメソッドを抽象メソッドと呼びます。抽象メソッドは定義のみを記述し、詳細内容は派生クラスで記述します。そして、抽象メソッドを含むクラスのことを抽象クラスと言います。

また、抽象クラスはインスタンス作成が出来ず、継承された派生クラスでは抽象メソッドを必ずオーバーライドしなければならない制約があります。

C#はabstractキーワードを使用します。

public abstract class TClass {
    public abstract void Method1();
}
public class Class5 : TClass {
    public override void Method1() {
        //処理内容
    }
}

VB.NETはMustInheritキーワードをクラス名につけ、MustOverrideをキーワードをメソッド名につけます。

Public MustInherit Class TClass
    Public MustOverride Sub Method1()
End Class
Public Class Class5
    Inherits TClass

    Public Overrides Sub Method1()
        '処理内容
    End Sub
End Class



抽象クラスは、
 ・基底クラスはインスタンス化させたくない(既定実装が無い)
 ・派生クラスが複数ある前提
 ・派生先で処理をオーバーライドさせる必要がある
 ・抽象メンバ以外のメンバも持たせる

これらを満たす場合は非常に有効です。

電卓を作ろうの手引き⑪(継承を利用した方法①)

デリゲートで演算メソッドを定義していた演算クラスたちを継承による方法に変更します。
まず、抽象的な何もしないクラスを定義します。繰り返しになりますが、今回はこのクラスもインスタンスとして使いますので抽象クラスにはしません。

計算メソッドであるCalcメソッドは演算によって振る舞いが変わりますので、オーバーライド出来るように仮想メソッドにしておきます。

public class Calc_None {
    public string ErrorMessage { get; protected set; } = string.Empty;
    public decimal NumLeft { protected get; set; } = 0;
    public virtual decimal? calc(decimal numRight) {
        return numRight;
    }
}
Public Class Ope_None
    Private errNaiyo As String = String.Empty
    Public Property ErrorMessage() As String
        Get
            Return errNaiyo
        End Get
        Protected Set(value As String)
            errNaiyo = value
        End Set
    End Property

    Private num As Decimal = 0
    Public Property NumLeft() As Decimal
        Protected Get
            Return num
        End Get
        Set(value As Decimal)
            num = value
        End Set
    End Property

    Public Overridable Function Calc(ByVal numRight As Decimal) As Decimal?
        Return numRight
    End Function
End Class



あとは、このCalc_Noneクラスを四則演算クラスに派生させます。

可変長引数②

可変長引数は引数無しで呼べる

可変長引数のメソッドに対してなにも指定せずメソッドを呼ぶと、nullが渡されます。つまり、メソッド側は空配列が作成されます。

private void TotalSum3(params int[] values) {
    Console.WriteLine(values.Count());
}

public static void Main() {
    TotalSum3();
}
Private Sub TotalSum3(ParamArray values As Integer())
    Console.WriteLine(values.Count())
End Sub

Public Sub Main()
    TotalSum3()
End Sub
0


可変長引数で指定できるのは配列のみ

System.Collections.Generic名前空間であるIEnuemrable インターフェイスは配列のような性質を持っていますが可変長引数で指定することはできません。当然、派生であるListクラスも利用できません。

以下はコンパイルエラーになります。

private void TotalSum(params IEnumerable<int> values) {

private void TotalSum(params List<int> values) {
Private Sub TotalSum(ParamArray values As IEnumerable(Of integer))

Private Sub TotalSum(ParamArray values As List(Of Integer))

可変長引数①

ちょっと難しい言葉ですが、要はメソッドに渡せる引数の数が可変ということです。

例えば以下のような同じ種の引数を複数必要とするメソッドを考えます。単純に引数を足す処理です。
引数は配列となっているので、当然メンバー変数を一旦配列に変換して引数に渡す必要があります。

private void TotalSum1(int[] values) {
    int res = 0;
    foreach(int v in values) {
        res += v;
    }
    Console.WriteLine(res);
}

public static void Main() {
    int a = 1;
    int b = 3;
    int c = 5;
    int d = 7;

    int[] values = { a, b, c, d};
    TotalSum1(values);
}
Private Sub TotalSum1(ByVal values As Integer())
    Dim res As Integer = 0
    For Each v As Integer In values
        res += v
    Next
    Console.WriteLine(res)
End Sub

Public Sub Main()
    Dim a As Integer = 1
    Dim b As Integer = 3
    Dim c As Integer = 5
    Dim d As Integer = 7

    Dim values As Integer() = {a, b, c, d}
    TotalSum1(values)
End Sub

これを可変長引数にすると、呼び出し元で配列に変換する必要が無くなります。
C#はparamsキーワードを利用します。

private void TotalSum2(params int[] values) {
    int res = 0;
    foreach (int v in values) {
        res += v;
    }
    Console.WriteLine(res);
}

public static void Main() {
    TotalSum2(a, b, c, d);
}

VB.NETはByValの代わりにParamArrayキーワードを指定します。

Private Sub TotalSum2(ParamArray values As Integer())
    Dim res As Integer = 0
    For Each v As Integer In values
        res += v
    Next
    Console.WriteLine(res)
End Sub

Public Sub Main()
    TotalSum2(a, b, c, d)
End Sub

引数で勝手に配列にしてくれるようなイメージですね。

回復パーティションの削除

パーティション削除はディスク管理より行えますが、特殊なパーティション、例えば回復パーティションなどは削除できません。

今回業務用のパソコン設定でパーティションが2つ必要なのですが、既に4つパーティションが存在する状況でしたので、どうしても一つパーティションを削除しなければならない状況になりました。

幸い、リカバリディスクが存在したことと、元々Windows7からWindows10へアップグレードした端末でしたので、余計な回復パーティションが削除できる状況でした。


回復パーティションのような特殊なパーティションを削除する場合は、Diskpartコマンドを利用します。

C:\Users\Sasaki>diskpart

Microsoft DiskPart バージョン 6.3.9600

Copyright (C) 1999-2013 Microsoft Corporation.
コンピューター: SasakiPC

DISKPART>

list diskより、対象ディスク番号をselect diskで選択します。

DISKPART> list disk

  ディスク      状態           サイズ   空き   ダイナ GPT
  ###                                          ミック
  ------------  -------------  -------  -------  ---  ---
  ディスク 0    オンライン           558 GB  1024 KB        *
  ディスク 1    オンライン            20 GB      0 B
  ディスク 2    オンライン            20 GB      0 B

DISKPART> select disk 0

ディスク 0 が選択されました。

DISKPART>

次にlist partitionでパーティション一覧を表示して、select partiton で削除対象パーティションを選択します。

DISKPART> list partition

  Partition ###  Type                Size     Offset
  -------------  ------------------  -------  -------
  Partition 1    システム              100 MB  1024 KB
  Partition 2    回復                 128 MB   101 MB
  Partition 3    プライマリ            500 GB   229 MB
  Partition 4    回復                  58 MB   500 GB

DISKPART> select partition 4

パーティション 4 が選択されました。

DISKPART>

最後に delete partitionで削除を実行します。パラメータにoverrideを指定します。

DISKPART> delete partition override

DiskPart は選択されたパーティションを正常に削除しました。

DISKPART>

なお、overrideパラメータを設定して強制削除にしないと、以下のエラーメッセージが表示されて削除が出来ません。

force protected パラメーターを設定しないと、保護されたパーティションは削除できません