Reputation: 11773
UPDATE: since inquiring minds want to know, here's some context as to why I'm needing to do what I'm doing:
My C# application (app A) interfaces with another application (app B, which is not mine) that sends me a Parent Id, a path to navigate to within the Parent's object graph, and a value to set once it gets there. App B has no notion of the Ids of the nested objects within the Parent model in the C# app, it has no notion that there is more than one instance of a Parent; it's job is simply to accept low level requests from external devices and turn them into something a little higher level and hand them to app A. Each Child object in a Parent has a unique path- no two references will point to the same Child object. So B might send A something that looks like the following JSON object:
{
'Id' : 12345,
'Path' : 'ChildProperty.NestedChildProperty.AnotherNestedChildProperty',
'Value': 'Ligers are bred for their skills in magic.'
}
There are other options to solve this issue, such as navigating up from a leaf Child object or down from the Parent object, but those are far less efficient, both in terms of database queries and application processing, than being able to query a repository of Children by the Id of the parent they belong to and their path within the Parent's object graph.
The example below is barebones. It doesn't even illustrated nested Child objects. It is as small as possible to illustrate the problem presented in the question without the context provided up to this point.
So with that...
Let's say I have a couple of classes like so:
public class Parent
{
public Child ChildProperty {get; set;}
}
public class Child
{
public string ParentPropertyName {get; protected set;}
}
It is worth noting that a Child will never be referenced by more than one Parent for this model. What I want to do is make the Child
instance aware of the property name it is being assigned to, so with this call:
parent.ChildProperty = new Child();
I have
parent.ChildProperty.ParentPropertyName == "ChildProperty" //evaluates to true
I've been down some weird rabbit holes trying to use some combination of events and Expressions/MemberExpressions and factories and reflection and recursive graph traversal to try to make this work, but nothing does, simply for the fact that assignment to the Parent property is done after instantiation of the Child. Doing this manually on the child like so:
parent.ChildProperty = new Child("ChildProperty");
is not an option as it is too prone to error.
The best I've been able to come up with is a sort of middle ground that looks more or less like this:
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;
}
}
While this works and is a lot better than manually passing in a string, the real classes are more complicated with wider & deeper object graphs, and thus I'd have to copy that setter logic on every property on every class where it was needed. On top of that, it transfers a functionality to the Parent
that really belongs to the Child
.
So that brings me back to my question- is there a way to make the Child
instance aware of the property name on the Parent
to which it is being assigned without intervention from the Parent
instance?
Upvotes: 2
Views: 1234
Reputation: 73492
I don't think you can achieve it reliably. Not because of technical reason, but because of logical.
Assume you have:
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;
Now what do expect child.ParentPropertyName
name to be? ChildProperty
or ChildProperty2
?
Question as stands doesn't makes sense. It will be easy to answer if you can explain what is the intent.
Upvotes: 2
Reputation: 21998
I am not really understand this
parent.ChildProperty.ParentPropertyName == "ChildProperty"
Perhaps you simply need
public class Parent
{
public Child Child {get; set;}
}
public class Child
{
public Parent Parent {get; set;}
}
So that they can find each other. Add that text property for whatever reason, so you can do
parent.Child.WhateverProperty == "WhateverProperty"
So that brings me back to my question- is there a way to make the Child instance aware of the property name on the Parent to which it is being assigned without intervention from the Parent instance?
They must know about each other (so that my guess is somewhat right). And then:
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; }
}
}
If parent is missing, then you can return "Something"
.
Upvotes: 0
Reputation: 13706
With the extra context, I'm going to provide a completely separate answer which addresses it in an entirely different manner.
What you're given appears to be a flattened tree. That is, each node knows its own path, not just it's parent. You can convert that back to a tree when you make objects from it. Here's a Data
class which acts instead of both Parent
and Child
.
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);
}
}
}
Usage:
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);
It's up to you how to parse the Path
into it's discrete parts.
Note that this version doesn't provide a mechanism from going from a child up to its Parent, but it's easy enough to add a public Data(Data parent)
constructor instead of the empty one, and a property to store it.
Upvotes: 1
Reputation: 13706
I still want to know why you want to do this, but Reflection is your answer. There are two steps to this:
First, the Child
has to know what Parent
to look at. This can be accomplished in one of two ways. Second, the Child
has to know how to get the name from the Parent
- this is pretty straight forward.
To address the second part first, add this function to the Child
object:
private static string GetParentPropertyName(Parent parent, Child child)
{
var matches = parent.GetType().GetProperties()
.Where(x => x.GetValue(parent, null) == child);
return matches.Single().Name;
}
This will check all properties on the specified Parent
to find the one that's storing the specified Child
. Then it will return the Name
of that property. Note that as written, this will throw an exception if there is not exactly one property holding the Child
. Adjust as appropriate to your scenario.
For the first part, there's two ways you can call this function. Which is best depends on your usage.
If you're going to guarantee a 1:1 relationship, then the best way is to pass the Parent
to the Child
when it's created. Your Child
object will look like this:
public class Child
{
private Parent _parent;
public Child(Parent parent)
{
_parent = parent;
}
public string ParentPropertyName {
get { return GetParentPropertyName(_parent, this); }
}
}
I'd suggest automatically creating the Child
when Parent.Child
is first accessed or when Parent
is first created.
If you don't want to guarantee that relationship, add the following function to your Child
instead of having the ParentPropertyName
property:
public string GetParentPropertyName(Parent parent)
{
return GetParentPropertyName(parent, this);
}
You can then add a function or property to the Parent
which does
return this.Child.GetParentPropertyName(this);
Upvotes: 0