c#在对象解构期间获取变量的名称
c# Get name of variable(s) during object deconstruction
鉴于此代码:
var (c, d) = new Test();
这可以从 Deconstruct
方法中获取变量名称吗?
public class Test
{
public void Deconstruct(out string value1, out string value2)
{
// is there a way to know the parameters are
// being mapped to "c" and "d" respectively
// inside this method?
}
}
重构以下代码以减少重复量的想法:
var parsed = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\d+?): (?<message>\d+?):")
.Apply(m => !m.Success ? null : new
{
// notice the names are repeated on both side
ID = m.Group["id"].Value,
Level = m.Group["level"].Value,
Message = m.Group["message"].Value,
});
我想用 Test
解决的问题 class:
var (id, level, message) = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?):")
.Groups
.AsDeconstructable(); //
我真的不认为这是个好主意,反射可能非常慢,但我们开始吧。
首先,我们需要一些扩展来使处理属性和字段更简洁:
public static class HelperExtensions {
// ***
// *** Type Extensions
// ***
public static List<MemberInfo> GetPropertiesOrFields(this Type t, BindingFlags bf = BindingFlags.Public | BindingFlags.Instance) =>
t.GetMembers(bf).Where(mi => mi.MemberType == MemberTypes.Field | mi.MemberType == MemberTypes.Property).ToList();
// ***
// *** MemberInfo Extensions
// ***
public static void SetValue<T>(this MemberInfo member, object destObject, T value) {
switch (member) {
case FieldInfo mfi:
mfi.SetValue(destObject, value);
break;
case PropertyInfo mpi:
mpi.SetValue(destObject, value);
break;
default:
throw new ArgumentException("MemberInfo must be of type FieldInfo or PropertyInfo", nameof(member));
}
}
public static TOut Apply<TIn, TOut>(this TIn m, Func<TIn, TOut> applyFn) => applyFn(m);
}
然后,我们需要创建一个class来表示想要的结果:
public class ParsedMessage {
public string ID;
public string Level;
public string Message;
}
现在,我们编写扩展以将 Group
命名值映射到对象中的属性或字段:
public static class MatchExt {
public static T MakeObjectFromGroups<T>(this Match m) where T : new() {
var members = typeof(T).GetPropertiesOrFields().ToDictionary(pf => pf.Name.ToLower());
var ans = new T();
foreach (Group g in m.Groups) {
if (members.TryGetValue(g.Name.ToLower(), out var mi))
mi.SetValue(ans, g.Value);
}
return ans;
}
public static string[] MakeArrayFromGroupValues(this Match m) {
var ans = new string[m.Groups.Count-1];
for (int j1 = 1; j1 < m.Groups.Count; ++j1)
ans[j1-1] = m.Groups[j1].Value;
return ans;
}
}
最后,我们可以使用我们的新扩展了:
var parsed = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?)")
.Apply(m => m.Success ? m.MakeObjectFromGroups<ParsedMessage>() : null);
注意:可以在运行时即时创建匿名类型,但它们很少有用。由于您不知道代码中其他任何地方的属性是什么,因此您必须通过反射来完成所有操作,除非您在像 ASP.Net 这样的反射密集环境中使用对象,否则您不妨使用 Dictionary
,或者如果必须的话,一个 DynamicObject
(不过,同样,不知道不太实用的字段名称)。
我添加了一个额外的扩展来将 Group
s 映射到 string[]
。由于 ValueTuple
字段的名称仅在编译时可用,因此创建数组并使用索引与创建 ValueTuple
并使用 Item1
等
一样好
最后,尝试使用匿名对象。通过传入匿名对象的模板,您可以从具有匹配名称的捕获组值创建新的匿名对象。
使用方法扩展进行类型推断:
public static class ToAnonymousExt {
public static T ToAnonymous<T>(this T patternV, Match m) {
var it = typeof(T).GetPropertiesOrFields();
var cd = m.Groups.Cast<Group>().ToDictionary(g => g.Name, g => g.Value);
return (T)Activator.CreateInstance(typeof(T), Enumerable.Range(0, it.Count).Select(n => cd[it[n].Name]).ToArray());
}
}
现在您可以将匿名类型作为模板传入,并返回填充的匿名对象。注意只有匿名类型中匹配捕获组名的字段才会被填充,不做任何运行时错误处理
var parsed3 = Regex.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?)")
.Apply(m => m.Success ? new { message = "", id = "", level = "" }.ToAnonymous(m) : null);
在 C# 10 中,您可以在调用中使用新属性 CallerArgumentExpression
结合 out
变量声明来接近您想要的内容:
首先,一个string
扩展方法:
public static class StringExt {
public static string Past(this string s, string separator) {
var starterPos = s.IndexOf(separator);
return starterPos == -1 ? s : s.Substring(starterPos + separator.Length);
}
}
现在一个示例 ToVariables
GroupCollection
的 arity 3 的扩展方法(显然很容易创建其他 arities):
public static class GroupCollectionExt {
public static void ToVariables(this GroupCollection gc, out string v1, out string v2, out string v3,
[CallerArgumentExpression("v1")] string name1 = "", [CallerArgumentExpression("v2")] string name2 = "", [CallerArgumentExpression("v3")] string name3 = "") {
v1 = gc.TryGetValue(name1.Past(" "), out var g1) ? g1.Value : default;
v2 = gc.TryGetValue(name2.Past(" "), out var g2) ? g2.Value : default;
v3 = gc.TryGetValue(name3.Past(" "), out var g3) ? g3.Value : default;
}
}
现在您可以声明变量并将它们分配给来自 Regex
匹配的组值,模式中只有重复:
Regex
.Match("123: qwe: qweasd:", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?):")
.Groups
.ToVariables(out var id, out var level, out var message);
注意:一个明显的扩展是为 Dictionary<string,T>
创建 ToVariables
。另一个扩展可能是在没有命名组时回退到位置组。
鉴于此代码:
var (c, d) = new Test();
这可以从 Deconstruct
方法中获取变量名称吗?
public class Test
{
public void Deconstruct(out string value1, out string value2)
{
// is there a way to know the parameters are
// being mapped to "c" and "d" respectively
// inside this method?
}
}
重构以下代码以减少重复量的想法:
var parsed = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\d+?): (?<message>\d+?):")
.Apply(m => !m.Success ? null : new
{
// notice the names are repeated on both side
ID = m.Group["id"].Value,
Level = m.Group["level"].Value,
Message = m.Group["message"].Value,
});
我想用 Test
解决的问题 class:
var (id, level, message) = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?):")
.Groups
.AsDeconstructable(); //
我真的不认为这是个好主意,反射可能非常慢,但我们开始吧。
首先,我们需要一些扩展来使处理属性和字段更简洁:
public static class HelperExtensions {
// ***
// *** Type Extensions
// ***
public static List<MemberInfo> GetPropertiesOrFields(this Type t, BindingFlags bf = BindingFlags.Public | BindingFlags.Instance) =>
t.GetMembers(bf).Where(mi => mi.MemberType == MemberTypes.Field | mi.MemberType == MemberTypes.Property).ToList();
// ***
// *** MemberInfo Extensions
// ***
public static void SetValue<T>(this MemberInfo member, object destObject, T value) {
switch (member) {
case FieldInfo mfi:
mfi.SetValue(destObject, value);
break;
case PropertyInfo mpi:
mpi.SetValue(destObject, value);
break;
default:
throw new ArgumentException("MemberInfo must be of type FieldInfo or PropertyInfo", nameof(member));
}
}
public static TOut Apply<TIn, TOut>(this TIn m, Func<TIn, TOut> applyFn) => applyFn(m);
}
然后,我们需要创建一个class来表示想要的结果:
public class ParsedMessage {
public string ID;
public string Level;
public string Message;
}
现在,我们编写扩展以将 Group
命名值映射到对象中的属性或字段:
public static class MatchExt {
public static T MakeObjectFromGroups<T>(this Match m) where T : new() {
var members = typeof(T).GetPropertiesOrFields().ToDictionary(pf => pf.Name.ToLower());
var ans = new T();
foreach (Group g in m.Groups) {
if (members.TryGetValue(g.Name.ToLower(), out var mi))
mi.SetValue(ans, g.Value);
}
return ans;
}
public static string[] MakeArrayFromGroupValues(this Match m) {
var ans = new string[m.Groups.Count-1];
for (int j1 = 1; j1 < m.Groups.Count; ++j1)
ans[j1-1] = m.Groups[j1].Value;
return ans;
}
}
最后,我们可以使用我们的新扩展了:
var parsed = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?)")
.Apply(m => m.Success ? m.MakeObjectFromGroups<ParsedMessage>() : null);
注意:可以在运行时即时创建匿名类型,但它们很少有用。由于您不知道代码中其他任何地方的属性是什么,因此您必须通过反射来完成所有操作,除非您在像 ASP.Net 这样的反射密集环境中使用对象,否则您不妨使用 Dictionary
,或者如果必须的话,一个 DynamicObject
(不过,同样,不知道不太实用的字段名称)。
我添加了一个额外的扩展来将 Group
s 映射到 string[]
。由于 ValueTuple
字段的名称仅在编译时可用,因此创建数组并使用索引与创建 ValueTuple
并使用 Item1
等
最后,尝试使用匿名对象。通过传入匿名对象的模板,您可以从具有匹配名称的捕获组值创建新的匿名对象。
使用方法扩展进行类型推断:
public static class ToAnonymousExt {
public static T ToAnonymous<T>(this T patternV, Match m) {
var it = typeof(T).GetPropertiesOrFields();
var cd = m.Groups.Cast<Group>().ToDictionary(g => g.Name, g => g.Value);
return (T)Activator.CreateInstance(typeof(T), Enumerable.Range(0, it.Count).Select(n => cd[it[n].Name]).ToArray());
}
}
现在您可以将匿名类型作为模板传入,并返回填充的匿名对象。注意只有匿名类型中匹配捕获组名的字段才会被填充,不做任何运行时错误处理
var parsed3 = Regex.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?)")
.Apply(m => m.Success ? new { message = "", id = "", level = "" }.ToAnonymous(m) : null);
在 C# 10 中,您可以在调用中使用新属性 CallerArgumentExpression
结合 out
变量声明来接近您想要的内容:
首先,一个string
扩展方法:
public static class StringExt {
public static string Past(this string s, string separator) {
var starterPos = s.IndexOf(separator);
return starterPos == -1 ? s : s.Substring(starterPos + separator.Length);
}
}
现在一个示例 ToVariables
GroupCollection
的 arity 3 的扩展方法(显然很容易创建其他 arities):
public static class GroupCollectionExt {
public static void ToVariables(this GroupCollection gc, out string v1, out string v2, out string v3,
[CallerArgumentExpression("v1")] string name1 = "", [CallerArgumentExpression("v2")] string name2 = "", [CallerArgumentExpression("v3")] string name3 = "") {
v1 = gc.TryGetValue(name1.Past(" "), out var g1) ? g1.Value : default;
v2 = gc.TryGetValue(name2.Past(" "), out var g2) ? g2.Value : default;
v3 = gc.TryGetValue(name3.Past(" "), out var g3) ? g3.Value : default;
}
}
现在您可以声明变量并将它们分配给来自 Regex
匹配的组值,模式中只有重复:
Regex
.Match("123: qwe: qweasd:", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?):")
.Groups
.ToVariables(out var id, out var level, out var message);
注意:一个明显的扩展是为 Dictionary<string,T>
创建 ToVariables
。另一个扩展可能是在没有命名组时回退到位置组。