PG日誌

各記事はブラウザの横幅を1410px以上にすると2カラムの見出しが表示されます。なるべく横に広げてみてください。

ValueObjectでファイルパスとファイル名を区別する

string 変数が xxxFilePath と書いてあってファイル名しか入ってない、xxxFileName と書いてあったのに中身はファイルパスということが頻発したりこのstring型そもそも何が入ってるのかわからないなんて事が頻発したので対応策を考えました。プリミティブな変数の中身は自分で書いたコードならさておき他人が書いたコードだとすごい判別しづらいので ValueObject を使って型で判断したいと思います。

確認環境

  • VisualStudio2019 19.6
  • C# 8.0

実装コード

使い方

先に使い方です。こんな感じで使えます。readonlyな構造体です。

// string を代入できる
FileName fname = "sample.txt";
FilePath fpath = @"d:\sample.txt";

File.Exist(fname); // 既存の型と互換する

fname = fpath; // こういうことはできない

string value = fname; // stringに代入できる

折角型を使っても、宣言したときに入れ間違えたら終わりなので気を付けてください。どうしようもないです。

ファイル名

ファイル名を表す型です。

// FileName.cs

// ファイル名
public readonly struct FileName : IEquatable<FileName>
{
    readonly string value;

    public string Value => this.value;

    public FileName(string value) => this.value = value;

    public static implicit operator string(FileName value) => value.value;
    public static implicit operator FileName(string value) => new FileName(value);

    public bool Equals(FileName other) => this.value.Equals(other.value);

    public override bool Equals(object obj) => obj is FileName _obj && this.Equals(_obj);
    public override int GetHashCode() => this.value.GetHashCode();
    public override string ToString() => this.value;

    public static bool operator ==(in FileName x, in FileName y) => x.value.Equals(y.value);
    public static bool operator !=(in FileName x, in FileName y) => !x.value.Equals(y.value);
}

ファイルパス

ファイルパスを表す型です。

// FilePath.cs

// ファイルパス
public readonly struct FilePath : IEquatable<FilePath>
{
    readonly string value;

    public FilePath(string value) => this.value = value;

    public string Value => this.value;

    public static implicit operator string(FilePath value) => value.value;
    public static implicit operator FilePath(string value) => new FilePath(value);

    public bool Equals(FilePath other) => this.value.Equals(other.value);

    public override bool Equals(object obj) => obj is FilePath _obj && this.Equals(_obj);
    public override int GetHashCode() => this.value.GetHashCode();
    public override string ToString() => this.value;

    public static bool operator ==(in FilePath x, in FilePath y) => x.value.Equals(y.value);
    public static bool operator !=(in FilePath x, in FilePath y) => !x.value.Equals(y.value);
}

ディレクトリ名

ついでにディレクトリ名の型も作成しておきます。

// DirectoryName.cs

// ディレクトリ名
public readonly struct DirectoryName : IEquatable<DirectoryName>
{
    readonly string value;

    public string Value => this.value;

    public DirectoryName(string value) => this.value = value;

    public static implicit operator string(DirectoryName value) => value.value;
    public static implicit operator DirectoryName(string value) => new DirectoryName(value);

    public bool Equals(DirectoryName other) => this.value.Equals(other.value);

    public override bool Equals(object obj) => obj is DirectoryName _obj && this.Equals(_obj);
    public override int GetHashCode() => this.value.GetHashCode();
    public override string ToString() => this.value;

    public static bool operator ==(in DirectoryName x, in DirectoryName y) => x.value.Equals(y.value);
    public static bool operator !=(in DirectoryName x, in DirectoryName y) => !x.value.Equals(y.value);
}

ディレクトリパス

ディレクトリパス用の型です。

// DirectoryPath.cs

// ディレクトリパス
public readonly struct DirectoryPath : IEquatable<DirectoryPath>
{
    readonly string value;

    public string Value => this.value;

    public DirectoryPath(string value) => this.value = value;

    public static implicit operator string(DirectoryPath value) => value.value;
    public static implicit operator DirectoryPath(string value) => new DirectoryPath(value);

    public bool Equals(DirectoryPath other) => this.value.Equals(other.value);

    public override bool Equals(object obj) => obj is DirectoryPath _obj && this.Equals(_obj);
    public override int GetHashCode() => this.value.GetHashCode();
    public override string ToString() => this.value;

    public static bool operator ==(in DirectoryPath x, in DirectoryPath y) => x.value.Equals(y.value);
    public static bool operator !=(in DirectoryPath x, in DirectoryPath y) => !x.value.Equals(y.value);
}

最後に

ただ、ValueObject → string への変換コンストラクターを implicit で宣言していますが以下のように若干ガバいのでそれが気になる場合は、implicit を explicit に変更したりそもそも変換コンストラクタを削除することもできます。

// これは簡単にできたほうがいい
FileName fileName ="asdf";

// 基本型にも簡単に代入できてしまう
string str = fileName;

// explicit に変更するとキャストしないと代入できなくなる
string str2 = (string)fileName;

// 基本クラスへの変換コンストラクタを削除して .Value 経由じゃないと取り出せないようにする
string str3 = fileName.Value;

こうすることでより安全に使う事ができるようになりますが、File.Copy(fileName.Value, (string)fileName2) のように記述しないといけなくなるので大幅に利用性が低下するのでここらへんはお好みでカスタマイズするようにしてください。

以上です。