C#のHasFlagメソッドとTypeScriptでのビットフラグの連携方法

※当サイトは、アフィリエイト広告を利用しています
C#(ASP.NET)
スポンサーリンク

この記事では、C#でビットフラグを扱う際に使われるHasFlagメソッドの基本的な使い方と、それをフロントエンド(JavaScript / TypeScript)と連携する方法について紹介します。

また連携時の注意点や実装例も含めて解説していきます。

この記事を読むメリット

  • ビットフラグを使ったenumの扱い方がフロントエンド(TS)で分かる
  • バックエンド・フロントエンド間のビットフラグのやり取り方法が分かる
スポンサーリンク

C#のHasFlagメソッドについて

まずはC#でのHasFlagメソッドについて紹介します。

HasFlagメソッドは基本的に複数選択肢を持った列挙型に対して使われます。

その列挙型の値が特定のビットフラグを含んでいるかを判定するメソッドです。

権限の種類を持った列挙型「Permisson」を例にしてみます。

[Flags]
enum Permission {
    None    = 0,
    Read    = 1,
    Write   = 2,
    Execute = 4,
}

var readOrWrite = Permission.Read | Permission.Write; // Read と Write を合わせた状態

bool hasRead = readOrWrite.HasFlag(Permission.Read);   // true
bool hasExecute = readOrWrite.HasFlag(Permission.Execute); // false

HasFlagを使うと、ビット演算で該当のフラグが立っているか判定することができます。

変数「readOrWrite」はReadとWriteのフラグが立っている状態なのでhasReadはtrueになり、hasExcuteではfalseが返ってきます。

注意点:2のべき乗で定義する理由

1点注意点として、複数選択を前提にしたenumでは定義する値は2のべき乗」である必要があります。

理由はフラグの組み合わせが重複してしまうからです。

enum Permission {
  Read = 1,    // 0001
  Write = 2,   // 0010
  Execute = 3  // 0011 ← Read + Write と衝突!
}

複数選択ができるビットフラグとして扱う場合は「2のべき乗」で定義するようにします。

スポンサーリンク

エンジニアにおすすめ書籍

エンジニアになりたて、これから勉強を深めていきたいという方におすすめの書籍はこちら!

フロントエンドとの連携

続いてはこの値をフロントエンドと連携する方法を紹介します。

webアプリではデータのやり取りで必ずといって良いほどバックエンドとフロントエンドの連携が必要です。

js/tsにはC#のHasFlagメソッドはないため同じような動きを別のコードで実現する必要があります。

先ほどのPermissonの例を使ってフロントエンドでの扱い方を紹介します。

TypeScript側の定義とUI実装

TypeScriptを前提にするのでまずは型定義から行います。

enum Permission {
  None = 0,
  Read = 1,
  Write = 2,
  Execute = 4
}

続いては、実際に画面上で複数選択を行なってバックエンドとの連携を行なってみます。

まずはUI部分で権限を複数選択して送信できる画面を作成しました。

権限を複数選択して送信できる画面

tsxファイルは以下のように作成しました。

import { useState } from 'react';

export default function HasFlagTest() {
  enum Permission {
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
  }

  // 選択肢
  const options = [
    { label: 'Read', value: Permission.Read },
    { label: 'Write', value: Permission.Write },
    { label: 'Execute', value: Permission.Execute },
  ];

  const [selectedFlags, setSelectedFlags] = useState<Permission>(Permission.None);

  const handleCheckboxChange = (value: Permission) => {
    setSelectedFlags((prev) => {
      // 既に選択されている場合は解除、されていない場合は追加
      return prev & value ? prev & ~value : prev | value;
    });
  };

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
      <h2 style={{ marginBottom: '20px' }}>権限の設定</h2>
      <div style={{ marginBottom: '20px' }}>
        <label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>
          権限を選択してください
        </label>
        <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
          {options.map((opt) => (
            <label
              key={opt.value}
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: '8px',
                cursor: 'pointer',
                padding: '8px',
                borderRadius: '4px',
                backgroundColor: selectedFlags & opt.value ? '#f0f0f0' : 'transparent',
                transition: 'background-color 0.2s',
              }}
            >
              <input
                type="checkbox"
                checked={(selectedFlags & opt.value) !== 0}
                onChange={() => handleCheckboxChange(opt.value)}
                style={{
                  width: '18px',
                  height: '18px',
                  cursor: 'pointer',
                }}
              />
              <span>{opt.label}</span>
            </label>
          ))}
        </div>
      </div>
      <button
        style={{
          padding: '8px 16px',
          backgroundColor: '#4CAF50',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: 'pointer',
          fontSize: '14px',
        }}
        onClick={() => console.log('Selected permissions:', selectedFlags)}
      >
        権限を送信
      </button>
    </div>
  );
}

バックエンドに送信するための処理

バックエンドにビットフラグで複数選択した値を送信するための処理が、チェックボックスの操作時に実行される「handleCheckboxChnage」メソッドの中身です。

この中身で、ビットフラグの処理を行なっています。

const [selectedFlags, setSelectedFlags] = useState<Permission>(Permission.None);

const handleCheckboxChange = (value: Permission) => {
  setSelectedFlags((prev) => {
    // 既に選択されている場合は解除、されていない場合は追加
    return prev & value ? prev & ~value : prev | value;
  });
};

ここでは、チェックボックスのつけ外しによって、「selectedFlags」にenumの値を追加したり削除しています。

prev & value」のビットANDでチェックした対象のvalueが既存の「selectedFlags」に含まれているかを判定しています。

次に、含まれている場合の処理が「prev & ~value」、含まれていなかった場合の処理が

prev | value」になります。

含まれている場合、チェックを行うとその選択肢はチェックを外す必要あるので除外する必要があります。

  • |(OR) → フラグを「追加」
  • &(AND) → フラグを「確認」
  • ~(NOT) → フラグを「否定(削除)」

これでビットフラグで複数選択した値を準備することができました。

バックエンド側での処理

続いては、バックエンドで先ほど送信された値をどのように処理するかを見ていきます。

バックエンド側でAPIを作成します。

using Microsoft.AspNetCore.Mvc;

namespace backend.Controllers
{

  [ApiController]
  [Route("api/permissions")]
  public class PermissionController : ControllerBase
  {
      [Flags]
      public enum Permission
      {
          None = 0,
          Read = 1,
          Write = 2,
          Execute = 4
      }

      [HttpPost]
      public IActionResult Post([FromBody] int permissions)
      {
          // ビットフラグを Permission 型に変換
          var permissionFlags = (Permission)permissions;

          // 各権限の確認
          var hasRead = permissionFlags.HasFlag(Permission.Read);
          var hasWrite = permissionFlags.HasFlag(Permission.Write);
          var hasExecute = permissionFlags.HasFlag(Permission.Execute);

          // デバッグ用に権限の状態を出力
          Console.WriteLine($"Received permissions: {permissions}");
          Console.WriteLine($"Has Read: {hasRead}");
          Console.WriteLine($"Has Write: {hasWrite}");
          Console.WriteLine($"Has Execute: {hasExecute}");

          // 権限の状態をレスポンスとして返す
          return Ok(new
          {
              permissions = permissions,
              hasRead = hasRead,
              hasWrite = hasWrite,
              hasExecute = hasExecute
          });
      }
  }
}

「int permissions」でフロントで処理したビットフラグの値を受け取っています。

まずはそれをPermission側に変換します。

変換ができればあとはHasFlagメソッドを使って各権限に値が存在しているかをチェックすることができます。

参考に、以下画像のように「Write」「Excute」にチェックを入れて送信してみます。

「Write」「Excute」にチェックを入れて送信

バックエンド側のコンソールには期待通りの結果が出力されました。

Received permissions: 6
Has Read: False
Has Write: True
Has Execute: True

バックエンドから受け取る処理

最後はバックエンドから渡ってきたビットフラグの値から、権限の付いているものにチェックをつけるという処理をしていきます。

バックエンド側で「ReadとExcuteの権限を返す」APIを作成しました。

[HttpGet]
public IActionResult Get()
{
  // ReadとExcuteの権限を返す
  return Ok(new {
    permissions = Permission.Read | Permission.Execute
  });
}

帰ってきた値を基に、先ほどの選択肢からReadとExcuteにチェックが入るようにしていきます。

import { useEffect, useState } from 'react';

export default function HasFlagTest() {
  enum Permission {
    None = 0,
    Read = 1,
    Write = 2,
    Execute = 4,
  }

  // 選択肢
  const options = [
    { label: 'Read', value: Permission.Read },
    { label: 'Write', value: Permission.Write },
    { label: 'Execute', value: Permission.Execute },
  ];

  const [selectedFlags, setSelectedFlags] = useState<Permission>(Permission.None);

  const handleCheckboxChange = (value: Permission) => {
    setSelectedFlags((prev) => {
      // 既に選択されている場合は解除、されていない場合は追加
      return prev & value ? prev & ~value : prev | value;
    });
  };

  useEffect(() => {
    const fetchPermissions = async () => {
      const response = await fetch('http://localhost:5017/api/permissions');
      const data = await response.json();
      console.log(data);
      setSelectedFlags(data.permissions);
    };
    fetchPermissions();
  }, []);

  return (
    <div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
      <h2 style={{ marginBottom: '20px' }}>権限の受け取り</h2>
      <div style={{ marginBottom: '20px' }}>
        <label style={{ display: 'block', marginBottom: '8px', fontWeight: 'bold' }}>
          権限を選択してください
        </label>
        <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
          {options.map((opt) => (
            <label
              key={opt.value}
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: '8px',
                cursor: 'pointer',
                padding: '8px',
                borderRadius: '4px',
                backgroundColor: selectedFlags & opt.value ? '#f0f0f0' : 'transparent',
                transition: 'background-color 0.2s',
              }}
            >
              <input
                type="checkbox"
                checked={(selectedFlags & opt.value) !== 0}
                onChange={() => handleCheckboxChange(opt.value)}
                style={{
                  width: '18px',
                  height: '18px',
                  cursor: 'pointer',
                }}
              />
              <span>{opt.label}</span>
            </label>
          ))}
        </div>
      </div>
    </div>
  );
}

先ほどのフォームと似たような構成ですが、フォームではなく、useEffectでバックエンドからpermissionの値を受け取ってそれを「selectedFlags」にセットするようにしています。

権限フロント側での受け取り

各選択肢の中では、先ほど同様でビット演算を使って

checked={(selectedFlags & opt.value) !== 0}」とすることで

これでページアクセス時にRead、 Excuteにチェックが入った上になります。

まとめ

いかがだったでしょうか。

ビット演算を使うと、バックエンドとフロントエンドの値の連携がスムーズに行うことができますし、コードも簡潔にすることができます。

バックエンドでは受け取った整数値を enum にキャストするだけで HasFlag による判定が可能です。

自分はまだビット演算を完璧に理解したわけではないので使い方含め今後も理解を深めていきたいです。

タイトルとURLをコピーしました