如何使子对象知道父对象上分配给它的属性名称?

本文关键字:对象 属性 何使 分配 | 更新日期: 2023-09-27 18:03:46

UPDATE:既然好奇的人想知道,这里有一些关于为什么我需要做我正在做的事情的背景:

我的c#应用程序(应用程序A)与另一个应用程序(应用程序B,这不是我的)接口,它发送给我一个Parent Id,一个在Parent对象图中导航的路径,以及一个到达那里后要设置的值。应用程序B不知道c#应用程序中Parent模型中嵌套对象的id,它不知道Parent有多个实例;它的工作只是接受来自外部设备的低级请求,并将它们转换为稍微高级的东西,然后将它们交给应用程序a。父对象中的每个子对象都有一个唯一的路径-没有两个引用会指向相同的子对象。因此,B可能会向A发送如下JSON对象:

{
    'Id'   : 12345,
    'Path' : 'ChildProperty.NestedChildProperty.AnotherNestedChildProperty',
    'Value': 'Ligers are bred for their skills in magic.'
}

还有其他选项可以解决此问题,例如从叶子子对象向上导航或从父对象向下导航,但就数据库查询和应用程序处理而言,这些都远不如通过父对象所属的Id及其在父对象图中的路径来查询子存储库的效率高。

下面的例子是裸机。它甚至没有说明嵌套的子对象。在没有提供到目前为止的上下文的情况下,尽可能小地说明问题中提出的问题。

所以…


假设我有两个这样的类:

public class Parent
{
    public Child ChildProperty {get; set;}
}
public class Child 
{
    public string ParentPropertyName {get; protected set;}
}

值得注意的是,在此模型中,子节点永远不会被多个父节点引用。我想做的是让Child实例意识到它被分配给的属性名称,所以使用这个调用:

parent.ChildProperty = new Child();

parent.ChildProperty.ParentPropertyName == "ChildProperty" //evaluates to true

我已经下了一些奇怪的兔子洞试图使用事件和表达式/MemberExpressions和工厂和反射和递归图遍历的一些组合,试图使这项工作,但没有什么,只是为事实,分配到父属性是在子实例化后完成。在子节点上手动执行如下操作:

parent.ChildProperty = new Child("ChildProperty");

不能作为选项,因为它太容易出错。

我能想到的最好的是一种中间立场,看起来或多或少是这样的:

public class Parent
{
    private Child childProperty;
    public Child ChildProperty
    {
        get { return childProperty; }
        set
        {
            childProperty = value;
            if (childProperty.ParentPropertyName == null)
            {
                childProperty.SetParentPropertyName(() => this.ChildProperty);
            }
        }
    }
}
public class Child
{
    public string ParentPropertyName { get; protected set; }
    internal void SetParentPropertyName(Expression<Func<object>> exp)
    {
        this.ParentPropertyName = (((MemberExpression) (exp.Body)).Member).Name;
    }
}

虽然这可以工作并且比手动传递字符串要好得多,但实际的类更复杂,使用更宽的&更深入的对象图,因此我必须在需要的每个类的每个属性上复制setter逻辑。最重要的是,它将真正属于Child的功能转移到Parent

所以这让我回到了我的问题-是否有一种方法可以使Child实例意识到Parent上的属性名称,而不需要Parent实例的干预?

如何使子对象知道父对象上分配给它的属性名称?

我认为你不能可靠地实现它。不是因为技术原因,而是因为逻辑原因。

假设你有:

public class Parent
{
    public Child ChildProperty {get; set;}
    public Child ChildProperty2 {get; set;}
}
public class Child 
{
    public string ParentPropertyName {get; protected set;}
}
var child = new Child();
parent.ChildProperty = child;
parent.ChildProperty2 = child;

现在期望child.ParentPropertyName的名称是什么?ChildProperty还是ChildProperty2 ?

问题本身没有意义。如果你能解释你的意图,这个问题就很容易回答了。

对于额外的上下文,我将提供一个完全独立的答案,以完全不同的方式解决它。

你得到的似乎是一棵被压扁的树。也就是说,每个节点都知道自己的路径,而不仅仅是它的父节点。当你用它制作对象时,你可以把它转换回树。这是一个Data类,它代替了ParentChild

class Data
{
    public int ID { get; set; }
    public string Value { get; set; }
    public Dictionary<string, Data> Children { get; set; }
    public Data()
    {
        Children = new Dictionary<string, Data>();
    }
    public Data(int Id, string value) : this()
    {
        this.ID = Id;
        this.Value = value;
    }
    public Data this[string name]
    {
        get { 
            if (Children.ContainsKey(name)) return Children[name];
            else {
                Children.Add(name, new Data());
                return Children[name];
            }
        }
        set {
            if (Children.ContainsKey(name)) Children[name] = value;
            else Children.Add(name, value);
        }
    }
}

用法:

var data = new Data();
data["ChildProperty"]["NestedChildProperty"]["AnotherNestedChildProperty"] = 
       new Data(12345, "Ligers are bred for their skills in magic.");
Console.WriteLine(data["ChildProperty"]["NestedChildProperty"]["AnotherNestedChildProperty"].Value);

如何将Path解析为它的离散部分取决于您。

请注意,这个版本没有提供从子节点到父节点的机制,但是很容易添加一个public Data(Data parent)构造函数而不是空的构造函数,并添加一个属性来存储它。

我仍然想知道你为什么要这样做,但反射是你的答案。有两个步骤:

首先,Child必须知道Parent要看什么。这可以通过以下两种方式之一来实现。其次,Child必须知道如何从Parent获得名称-这非常直接。

首先处理第二部分,将这个函数添加到Child对象:

private static string GetParentPropertyName(Parent parent, Child child)
{
    var matches = parent.GetType().GetProperties()
                        .Where(x => x.GetValue(parent, null) == child);
    return matches.Single().Name;
}

这将检查指定Parent上的所有属性,以找到存储指定Child的属性。然后它将返回该属性的Name。请注意,如前所述,如果没有恰好有一个属性持有Child,则会抛出异常。根据您的场景进行适当的调整。

对于第一部分,有两种方法可以调用该函数。哪一个最好取决于你的用法。

  1. 如果您要保证1:1的关系,那么最好的方法是在创建Parent时将其传递给Child。你的Child对象看起来像这样:

    public class Child
    {
        private Parent _parent;
        public Child(Parent parent)
        {
            _parent = parent;
        }
        public string ParentPropertyName { 
            get { return GetParentPropertyName(_parent, this); } 
        }
    }
    

    我建议在首次访问Parent.Child或首次创建Parent时自动创建Child

  2. 如果您不想保证这种关系,请将以下函数添加到Child中,而不是拥有ParentPropertyName属性:

    public string GetParentPropertyName(Parent parent) 
    { 
       return GetParentPropertyName(parent, this); 
    }
    

    你可以在Parent中添加一个函数或属性来执行

    return this.Child.GetParentPropertyName(this);
    

我真的不明白这个

parent.ChildProperty.ParentPropertyName == "ChildProperty"

也许你只是需要

public class Parent
{
    public Child Child {get; set;}
}
public class Child 
{
    public Parent Parent {get; set;}
}

这样他们就能找到彼此。不管出于什么原因,添加text属性,这样就可以执行

parent.Child.WhateverProperty == "WhateverProperty"

所以这让我回到了我的问题-有没有一种方法可以使子实例意识到父实例上的属性名称,而不需要父实例的干预?

他们一定互相了解(所以我的猜测多少是对的)。然后:

public class Parent
{
    public Child Child {get; set;}
    public string WhateverProperty {get; set;}
}
public class Child 
{
    public Parent Parent {get; set;}
    public string WhateverProperty
    {
        get { return Parent.WhateverProperty; }
        set { Parent.WhateverProperty = value; }
    }
}

如果缺少父元素,则可以返回"Something"