c#在对象解构期间获取变量的名称

时间:2018-11-30 20:13:01

标签: c# c#-7.0

给出以下代码:

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(); // 

1 个答案:

答案 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());
    }
}