【问题标题】:c# Get name of variable(s) during object deconstructionc# 在对象解构期间获取变量的名称
【发布时间】:2018-11-30 20:13:01
【问题描述】:

鉴于此代码:

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

【问题讨论】:

  • 我不知道(但我说这并不排除存在一些过于复杂和讨厌的黑客/技巧)。但对我来说,整个事情听起来像是一个 XY 问题。你到底想用var (c, d) = new Test(); 这样的行来实现什么?
  • @elgonzo 我已经用真正的问题更新了我的问题。
  • 请注意,您的元组示例与dynamic 示例具有相同的名称重复。您只是稍微移动了目标:不必在 Apply 方法中复制组名,现在必须在元组解构中复制组名。不同的地方,相同的名字重复。
  • 如果没有大量工作,您无法在运行时创建匿名类,但您可以使用另一种结构,例如 Dictionary。另外,我建议您对重复存在误解 - Level 作为匿名类型字段和 "level" 作为字符串常量之间存在很大差异。
  • 我不知道你在说什么。无论您在方法中使用元组解构还是其他方法,如果您不想使用/返回组集合本身,则必须将 m.Group["id"].Value 的值分配给某些东西。为什么分配会导致您可以使用result.id 与仅id 之类的东西对您来说如此重要?这感觉更像是你无缘无故地抓痒;或者我完全误解了你想用这个去哪里......

标签: c# c#-7.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 这样的反射繁重的环境中使用对象,否则您不妨使用Dictionary,或者如果你必须,DynamicObject(不过,同样,不知道不太实用的字段名称)。

我添加了一个额外的扩展来将Groups 映射到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);

【讨论】:

  • 匿名类是为了避免一开始就创建一个类,所以这违背了目的。我想转到Deconstruct 或命名元组,因为它们比匿名类更简洁。 (如:.ToValueTuple&lt;(string id, string level)&gt;())。 +1 努力。
  • @Xiaoy312 与匿名对象不同,ValueTuples 中的字段仅在编译时构造。它们不会出现在反射中,也不能(轻松)在运行时访问。如果匿名元组没问题,那可能是可以做到的,也许有点痛苦。
  • @Xiaoy312 因为ValueTuples 在运行时无论如何都没有名字,而且由于匹配值都是strings,所以我添加了一个string[] 版本的扩展。
  • 是的,在运行时,named`ValueTuple` 和 Deconstruct 方法的名称都会被删除。我放弃了那条路线。拥有一个字符串数组确实不会给代码带来太多改进,尤其是因为映射丢失了。
  • @Xiaoy312 那么,Dictionary 有什么问题呢?顺便说一句,即使您可以在运行时访问这些名称(例如反射),您将在代码中使用什么/如何使用它?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-09
  • 1970-01-01
  • 1970-01-01
  • 2019-07-08
  • 1970-01-01
  • 2016-08-03
相关资源
最近更新 更多