给出以下代码:
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
类解决的问题:
var (id, level, message) = Regex
.Match("123: qwe: qweasd", @"(?<id>\d+?): (?<level>\w+?): (?<message>\w+?):")
.Groups
.AsDeconstructable(); //
答案 0 :(得分:1)
我真的不认为这是个好主意,反射可能会非常慢,但是我们开始吧。
首先,我们需要一些扩展以使属性和字段的处理更加简洁:
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);
}
然后,我们需要创建一个类来表示所需的结果:
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等反射密集型环境中使用这些对象,否则不妨使用{{1} },或者如果必须的话,使用Dictionary
(尽管如此,但又不知道不太实用的字段名称)。
我添加了一个附加扩展名,以将DynamicObject
映射到Group
。由于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());
}
}