#はじめに
C#のGenericは非常によく利用されます。
このGenericはwhereによって指定できる型をある程度制限できますが、こと値型のみに限定するということができません。
ここで言う値型とは
- byte
- sbyte
- short
- ushort
- int
- uint
- long
- ulong
- float
- double
- decimal
を指します。
ただし、これらの型が共通で継承しているinterfaceをwhereで指定することでそこそこに限定できるようです。
#確認環境
Unity 2019.2.0f1
IL2CPP
Android
#実装
実装としてはこれだけです。
例はメソッドですがクラスでも一緒です。
void Hoge<T>() where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
}
これらのinterfaceを全て実装している独自のstructなどがあればGenericに指定できてしまいますが、ほぼほぼ無いと思うのでこれで大丈夫でしょう。
実際に最初に記述した値型以外の型を指定するとコンパイルエラーになります。
#参考
https://stackoverflow.com/questions/805264/how-to-define-generic-type-limit-to-primitive-types
#注意
全ての型のリファレンスを確認したわけではないのでもしかしたらこれらの型以外にも一致するものがあるかもしれません。
#おまけ
この制約を利用して指定した型で合計と平均を集計するクラスを実装してみました。
##IL2CPPでの実装
計算を行う必要がありますが、Tのままでは計算ができないため、計算時は全てdecimalに変換しています。
namespace MyEngine
{
using System;
/// <summary>
/// データ集計管理クラス
/// </summary>
public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
#region Properties
/// <summary> 合計値 </summary>
public T Sum { get; private set; }
/// <summary> 平均値 </summary>
public T Average { get; private set; }
/// <summary> 集計データのサンプル数 </summary>
public int SampleNum { get; private set; }
#endregion
#region Constructor
public Aggregate()
{
Clear();
}
#endregion
#region Methods
private T ConvertValue(object data)
{
return (T)Convert.ChangeType(data, typeof(T));
}
#endregion
#region API
/// <summary>
/// 集計データを初期化する
/// </summary>
public void Clear()
{
SampleNum = 0;
Sum = ConvertValue(0);
Average = ConvertValue(0);
}
/// <summary>
/// 集計データを追加
/// </summary>
/// <param name="data"></param>
public void Add(T data)
{
SampleNum++;
Sum = ConvertValue(Sum.ToDecimal(null) + data.ToDecimal(null));
Average = ConvertValue(Sum.ToDecimal(null) / SampleNum);
}
#endregion
}
}
##Monoでの実装
Monoの場合はConvertをdynamicに置き換えることができます。
namespace MyEngine
{
using System;
/// <summary>
/// データ集計管理クラス
/// </summary>
public class Aggregate<T> where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
{
#region Properties
/// <summary> 合計値 </summary>
public T Sum { get; private set; }
/// <summary> 平均値 </summary>
public T Average { get; private set; }
/// <summary> 集計データのサンプル数 </summary>
public int SampleNum { get; private set; }
#endregion
#region Constructor
public Aggregate()
{
Clear();
}
#endregion
#region API
/// <summary>
/// 集計データを初期化する
/// </summary>
public void Clear()
{
SampleNum = 0;
Sum = (dynamic)0;
Average = (dynamic)0;
}
/// <summary>
/// 集計データを追加
/// </summary>
/// <param name="data"></param>
public void Add(T data)
{
SampleNum++;
Sum += (dynamic)data;
Average = (dynamic)Sum / SampleNum;
}
#endregion
}
}
##使用方法
適当なオブジェクトにアタッチしたら実行できます。
Aggregateのintを他の型に変えるとその型で集計するようになります。
試しにfloatにすると少数まで集計します。
PCの場合
左クリック : 一回のみ集計
右クリック : 毎フレーム集計
Android実機の場合
1本指でタッチ : 一回集計
2本指でタッチ : 毎フレーム集計
3本指でタッチ : 集計クリア
using UnityEngine;
using MyEngine;
using Random = System.Random;
public class AggregateTest : MonoBehaviour
{
Aggregate<int> _aggregate = new Aggregate<int>();
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
bool isUpdate = false;
#if UNITY_EDITOR
if (Input.GetMouseButtonDown(0) || Input.GetMouseButton(1))
{
isUpdate = true;
}
#else
if (Input.touchCount == 1 && Input.touches[0].phase == TouchPhase.Began)
{
isUpdate = true;
}
else if (Input.touchCount == 2)
{
isUpdate = true;
}
else if (Input.touchCount == 3)
{
_aggregate.Clear();
}
#endif
if (isUpdate)
{
Random rand = new Random();
_aggregate.Add((int)(rand.NextDouble() * 100));
}
}
private void OnGUI()
{
GUILayout.Label($"合計={_aggregate.Sum:N}\n平均={_aggregate.Average:N}\nサンプル数={_aggregate.SampleNum:N}");
}
}