首页 > 解决方案 > c# 在对象解构期间获取变量的名称

问题描述

鉴于此代码:

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

标签: c#c#-7.0

解决方案


我真的不认为这是一个好主意,反射可能非常慢,但我们开始吧。

首先,我们需要一些扩展来让属性和字段的处理更加简洁:

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 这样的反射繁重的环境中使用对象,否则您最好使用 a Dictionary,或者如果您必须,a DynamicObject(虽然,再次,不知道不太实用的字段名称)。

我添加了一个额外的扩展来将Groups 映射到string[]. 由于ValueTuple字段的名称仅在编译时可用,因此创建数组和使用索引与创建 aValueTuple和使用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);

推荐阅读