はじめに
この記事は見ないほうがいいです
もっといい記事見た方がいい、ChatGPTくんもそう言ってる
やること
簡単なコンソールアプリ作ってる際にDB操作してて
いちいちクラス作ってやるの面倒だなと感じて
ValueTupleに対して、SetValueってできないかなと思い
Dapperでやれないかと見てたところサポートはしてないっぽいけど
出来てたので、じゃあどうやったらいいのか見た結果をメモ的に
ではどうするか
大方のSetValueするやり方としては
object obj = new T();
var properties = typeof(T).GetProperties();
var hogeMemberInfo = properties.First(x => x.Name == "hoge");
hogeMemberInfo.SetValue(obj, "piyo");
といった感じで
Tクラスのインスタンス生成
そこからリフレクションによりPropertyInfoやMemberInfoを取得して
入れていく感じになると思われます
結局のところ、ValueTupleも同じ感じで
- ValueTupleか確認する(これは必要であればでOK 「ValueTuple`(ValueTupleのItem個数)」となってるはず)
- ValueTupleのインスタンス生成(ConstructorInfoを取得すればOK)
- ValueTupleの中にある型(stringやらintやら)を見てPropertyInfo取る
- PropertyInfoにSetValueする
気をつけたいのはC#でおなじみの方のタプルの記事(https://ufcpp.net/study/csharp/datatype/tuples/)でも分かる通り
ValueTupleの内部実装は
1個目から7個目まではItem1~7、8個目以降はRest.Item1~7となっていること
Rest以降に足りない場合は更にRestが続く
要はRestがある場合はその分もConstructorInfoを取ってやる必要があるので
順番としては
- ValueTupleのConstructorInfoを取得する
- 1でRestがある場合はRest内のConstructorInfoを取得する
- 取得したConstructorInfo群から逆順でインスタンス生成を行う(Restの中身から作っていくため)
- 生成したインスタンスに対してPropertyInfoを取得しSetValueを行う
と言った感じでこんな感じにテキトーにDapperのソースを参考に
// ValueTupleのConstructorInfoをRest含めて取得する
private (IEnumerable<ConstructorInfo>, IEnumerable<Type[]>) GetValueTupleConstructorsInfo(Type valueTupleType)
{
var constructors = new List<ConstructorInfo>();
var currentType = valueTupleType;
var constructorFieldInfos = new List<Type[]>();
while (true)
{
var restField = (FieldInfo)null;
var valueTupleFields = currentType.GetFields(BindingFlags.Public | BindingFlags.GetField | BindingFlags.Instance | BindingFlags.DeclaredOnly);
var arity = valueTupleFields.Any(x => x.Name == "Rest") ? valueTupleFields.Length - 1 : valueTupleFields.Length;
var valueTupleFieldTypes = new Type[arity];
for (int i = 0; i < valueTupleFields.Length; i++)
{
var info = valueTupleFields[i];
if (info.Name == "Rest")
{
restField = info;
}
else if (info.Name.StartsWith("Item"))
{
var convertType = Nullable.GetUnderlyingType(info.FieldType) ?? info.FieldType;
valueTupleFieldTypes[i] = convertType;
}
}
var constructorArgs = restField != null ? valueTupleFieldTypes.Append(restField.FieldType).ToArray() : valueTupleFieldTypes;
constructors.Add(currentType.GetConstructor(constructorArgs));
constructorFieldInfos.Add(valueTupleFieldTypes);
if (restField is null) break;
currentType = restField.FieldType;
}
return (constructors, constructorFieldInfos);
}
//
private void SetValueToValueTuple(IEnumerable<ConstructorInfo> constructors, IEnumerable<object[]> itemValueList, ref object obj)
{
// Restにしているものから作成
var value = (object)null;
var restValue = (object)null;
for (int i = constructors.Count() - 1; i >= 0; i--)
{
var constructor = constructors.ElementAt(i);
var arguments = itemValueList.ElementAt(i);
if (restValue != null)
{
arguments = arguments.Append(restValue).ToArray();
}
value = Convert.ChangeType(constructor.Invoke(arguments), constructor.DeclaringType);
restValue = value;
}
obj = value;
}
結局のところ
需要はないと思う
というか、C#やってる人だったら誰でもわかっていることだと思うので
こんな記事書く必要もないんだろうなと
一応どっかのタイミングでGitHubには上げる予定