【C#】基本型に範囲チェック機能を追加する

はじめに

ある変数が範囲内に収まっていれば新しい値を代入する処理などの「範囲を意識した処理」というものはプログラミングをしているを割と良く出てくる課題です。コードを書くと以下のように記述できます。

// value が 0~10の範囲内なら新しい値を代入する
double value = 10.5;
if (value <= 10 && value >= 0)
{
    value = 21.5;
}

こういった判定の繰り返しの記述で毎回コードを書かないように処理を汎用化したいと思います。この処理を各型の「拡張メソッド」として定義して上記判定文を以下のように簡単化します。

// value が 0~10の範囲内なら新しい値を代入する
value.Assign(21.5, 0, 10);

目次

今回の記事の目次は以下の通りです。

確認環境

  • VisualStudio 2019
  • .NET Core 3.1 (C#8.0)
  • Windows 10

実装した処理一覧

今回実装したメソッドの概要です。

# メソッド名 説明
1 Assign(v, min, max) 変数値が min ~ max の範囲内なら v を代入して true / 範囲外の場合代入せずに false を返す
2 AssignIfOrMore(v, min) 変数値が min 以上なら v を代入して true / 範囲外の場合代入せずに false を返す
3 AssignIfOrLess(v, max) 変数値が min 以下なら v を代入して true / 範囲外の場合代入せずに false を返す
4 IsInRange(min, max) 変数値が min ~ max の範囲内なら true / 範囲外なら false を返す
5 IsInRangeOrMore(min) 変数値が min 以上 true / 範囲外の場合代入せずに false を返す
6 IsInRangeOrLess(max) 変数値が min 以下なら true / 範囲外の場合代入せずに false を返す
7 Clamp(min, max) 変数値が範囲内に収まるように min以下ならmin, max 以上なら max の値を返す
8 ClampSelf(min, max) 変数値が範囲内に収まるように min以下ならmin, max 以上なら max の値を自分自身に設定する

上記メソッドのサポートしている型は以下の通りです。(.NETの基本型は全部サポートしました…大変だった、、、

# 型名 サポート状況
1 byte サポート済み
2 sbyte サポート済み
3 decimal サポート済み
4 double サポート済み
5 float サポート済み
6 int サポート済み
7 uint サポート済み
8 long サポート済み
9 ulong サポート済み
10 short サポート済み
11 ushort サポート済み

使い方

前述のメソッドの使い方は以下の通りです。

Assign(v, min, max):現在値が範囲内なら新しい値を代入する

// (1) 
int value_1 = 10;
bool ret_1 = value_1.Assign(30, 0, 10);
// > ret_1=true, value_1=30, 0~10の範囲外だったので新しい値を代入

int value_2 = 10;
bool ret_2 = value_2.Assign(30, 0, 5);
// > ret_2=flase, value_2=10, 0~5の範囲外なので変化しない

AssignIfOrMore(v, min):現在値が min 以上なら値を代入する

int value_1 = 10;
bool ret_1 = value_1.AssignIfOrMore(30, 0);
// > ret_1=true, value_1=30, 現在値が0以上だったので新しい値を代入

int value_2 = 10;
bool ret_2 = value_2.AssignIfOrMore(30, 20);
// > ret_2=flase, value_2=10, 現在値が20以上ではないので代入しない

AssignIfOrLess(v, max):現在値が min 以下なら値を代入する

int value_1 = 10;
bool ret_1 = value_1.AssignIfOrLess(30, 100);
// > ret_1=true, value_1=100, 現在値が100以下だったので新しい値を代入

int value_2 = 10;
bool ret_2 = value_2.AssignIfOrLess(30, 5);
// > ret_2=flase, value_2=10, 現在値が5以下ではないので代入しない

IsInRange(min, max):変数値が min ~ max の範囲内か確認する

int value_1 = 50;
bool ret_1 = value_1.IsInRange(0, 100);
// > ret_1=true, 変数値が0~100の範囲内なのでtrue

int value_2 = 200;
bool ret_2 = value_2.IsInRange(0, 100);
// > ret_2=flase, 変数値が0~100の範囲外なのでfalse

IsInRangeOrMore(min): 変数値が min 以上か確認する

int value_1 = 100;
bool ret_1 = value_1.IsInRangeOrMore(50);
// > ret_1=true, 変数値が50以上なのでtrue

int value_2 = 100;
bool ret_2 = value_2.IsInRangeOrMore(200);
// > ret_2=flase, 変数値が200以上ではないのでfalse

IsInRangeOrLess(max):変数値が min 以下か確認する

int value_1 = 50;
bool ret_1 = value_1.IsInRangeOrLess(100);
// > ret_1=true, 変数値が100以下なのでtrue

int value_2 = 50;
bool ret_2 = value_2.IsInRangeOrLess(30);
// > ret_2=flase, 変数値が30以下ではないのでfalse

Clamp(min, max):範囲内に収まる値を返す

int value_1 = 100;
int ret_1 = value_1.Clamp(20, 30);
// > ret_1=30, 30以上なので範囲内の最大値30を返す

int value_2 = 10;
int ret_2 = value_2.Clamp(20, 30);
// > ret_2=20, 20以下なので範囲内の最小値20を返す

ClampSelf(min, max):変数値を範囲内に収める

int value_1 = 100;
value_1.ClampSelf(20, 30);
// > value_1=30, 30以上なので範囲内の最大値30を設定する

int value_2 = 10;
value_2.ClampSelf(20, 30);
// > ret_2=20, 20以下なので範囲内の最小値20を設定する

実装コード

使い方の項目のメソッドを各基本型に拡張メソッドとして実装します。

RangeExtensionクラス

全部のコードがここに入っています。死ぬほど長いので全部コピペしてエディタ上で見たほうがいいかもしれません。

/// <summary>
/// 基本型の機能を拡張し、範囲に関係する機能を追加します。
/// </summary>
public static class RangeExtension
{
    // (1) 現在値が範囲内なら代入してtrue、範囲外の場合代入せずにfalse
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref byte self, byte newValue, byte min, byte max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref sbyte self, sbyte newValue, sbyte min, sbyte max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref decimal self, decimal newValue, decimal min, decimal max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref double self, double newValue, double min, double max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref float self, float newValue, float min, float max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref int self, int newValue, int min, int max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref uint self, uint newValue, uint min, uint max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref long self, long newValue, long min, long max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref ulong self, ulong newValue, ulong min, ulong max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref short self, short newValue, short min, short max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool Assign(this ref ushort self, ushort newValue, ushort min, ushort max)
    {
        if (self < min || self > max) return false;
        self = newValue;
        return true;
    }

    // (2) 現在値が最小値以上なら代入してtrue、下回っている場合代入せずにfalse
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref byte self, byte newValue, byte min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref sbyte self, sbyte newValue, sbyte min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref decimal self, decimal newValue, decimal min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref double self, double newValue, double min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref float self, float newValue, float min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref int self, int newValue, int min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref uint self, uint newValue, uint min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref long self, long newValue, long min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref ulong self, ulong newValue, ulong min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref short self, short newValue, short min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrMore(this ref ushort self, ushort newValue, ushort min)
    {
        if (self < min) return false;
        self = newValue;
        return true;
    }

    // (3) 現在値が最大値以下なら代入してtrue、上回っている場合代入せずにfalse
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref byte self, byte newValue, byte max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref sbyte self, sbyte newValue, sbyte max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref decimal self, decimal newValue, decimal max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref double self, double newValue, double max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref float self, float newValue, float max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref int self, int newValue, int max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref uint self, uint newValue, uint max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref long self, long newValue, long max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref ulong self, ulong newValue, ulong max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref short self, short newValue, short max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool AssignIfOrLess(this ref ushort self, ushort newValue, ushort max)
    {
        if (newValue > max) return false;
        self = newValue;
        return true;
    }

    // (3) 現在値が範囲内に収まっているか確認する、true : 範囲内 / false : 範囲外
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static bool IsInRange(this byte self, byte min, byte max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this sbyte self, sbyte min, sbyte max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this decimal self, decimal min, decimal max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this double self, double min, double max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this float self, float min, float max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this int self, int min, int max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this uint self, uint min, uint max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this long self, long min, long max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this ulong self, ulong min, ulong max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this short self, short min, short max) => max <= self && self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRange(this ushort self, ushort min, ushort max) => max <= self && self >= min;

    // (4) 現在値が指定した最小値以上かどうか確認する、true : 最小値以上 / false : 最小値未満
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this byte self, byte min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this sbyte self, sbyte min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this decimal self, decimal min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this double self, double min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this float self, float min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this int self, int min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this uint self, uint min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this long self, long min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this ulong self, ulong min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this short self, short min) => self >= min;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrMore(this ushort self, ushort min) => self >= min;

    // (5) 現在値が指定した最大値以下かどうか確認する、true : 最大値以下 / false : 最大値より大きい
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this byte self, byte max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this sbyte self, sbyte max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this decimal self, decimal max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this double self, double max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this float self, float max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this int self, int max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this uint self, uint max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this long self, long max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this ulong self, ulong max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this short self, short max) => max <= self;
    [MethodImpl(MethodImplOptions.AggressiveInlining)] 
    public static bool IsInRangeOrLess(this ushort self, ushort max) => max <= self;

    // (6) 自分自身から範囲内に収まるように値を取得します。
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static byte Clamp(this byte self, byte min, byte max)
    {
        byte value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static sbyte Clamp(this sbyte self, sbyte min, sbyte max)
    {
        sbyte value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static decimal Clamp(this decimal self, decimal min, decimal max)
    {
        decimal value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static double Clamp(this double self, double min, double max)
    {
        double value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static float Clamp(this float self, float min, float max)
    {
        float value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static int Clamp(this int self, int min, int max)
    {
        int value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static uint Clamp(this uint self, uint min, uint max)
    {
        uint value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static long Clamp(this long self, long min, long max)
    {
        long value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ulong Clamp(this ulong self, ulong min, ulong max)
    {
        ulong value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static short Clamp(this short self, short min, short max)
    {
        short value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static ushort Clamp(this ushort self, ushort min, ushort max)
    {
        ushort value = self;
        if (self < min) { value = min; }
        else if (self > max) { value = max; }
        return value;
    }

    // (7) 自分自身をが範囲外なら範囲内に収まるように調整します。
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref byte self, byte min, byte max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref sbyte self, sbyte min, sbyte max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref decimal self, decimal min, decimal max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref double self, double min, double max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref float self, float min, float max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref int self, int min, int max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref uint self, uint min, uint max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref long self, long min, long max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref ulong self, ulong min, ulong max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref short self, short min, short max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void ClampSelf(this ref ushort self, ushort min, ushort max)
    {
        if (self < min) { self = min; }
        else if (self > max) { self = max; }
    }
}