3

I have two objects, from same class type. They both have a property (and its private field). Conditionally, I would like to assign this property/field as a reference to the same property/field of another instance of the same class.

It's important to note that my requirement is to read the referenced field. Write to it would be a bonus, not a problem, and not necessary.

I would like to know if it would be possible to use a ref field, and do some logic inside the property getter.

Also, I know there has been some questions about this here on StackOverflow (here, here, here, here), but they are all pretty old (so the idea of duplicate does not seem to apply here).
Now we are on C# 10.0, and a lot of different ref capabilities had been implemented, like ref locals. I tried to use some of them, but I failed. Hope someone with more knowledge could help me to see a way to do it.

So, is it possible, without using a wrapper to link one instance's property to another?? (which is my current approach, detailed below)


More details:

That's my class (actually it's a huge simplification of it):

public class Person
{
    private string _fullName;
    public string FullName { get => _fullName; set => _fullName = value; }
}

And, let's say I have 3 instances of this class. Now, if I try to assign the value of one property to another, the value is simply copied (as expected. I already am fairly aware why it is):

var person01 = new Person() { FullName = "Steve" };
var person02 = new Person() { FullName = "Mr. S" };
var person03 = new Person() { FullName = "Mister Steve" };

person02.FullName = person01.FullName;
// person02.FullName is "Steve"
person01.FullName = "Mr. Steve Jobs";
// person01.FullName is "Mr. Steve Jobs"
// person02.FullName is "Steve" because the value was only copied.

I UNDERSTAND WHY IT IS. But I want to know if there is some elegant way to do this:

ref person02.FullName = ref person01.FullName;  // not allowed
ref person03.FullName = ref person02.FullName;  // not allowed

person01.FullName = "Mr. Steve Jobs";
// person01.FullName is "Mr. Steve Jobs"
// person02.FullName is "Mr. Steve Jobs"
// person03.FullName is "Mr. Steve Jobs"

So the idea is assign a field as a reference to another field of another object. I believe that through a Property it's not possible, since it's actually a "method" to get and set the private field.

So i did this, it works, but it's not pretty:

public class Person
{
    private string _fullName;
    public string FullName
    {
        get => (FullName_Reference == null ? _fullName : FullName_Reference.FullName);
        set => _fullName = value;
    }
    public Person FullName_Reference { get; set; }

    public Person()
    {
        _fullName = "";
        FullName_Reference = null;
    }
}

var person01 = new Person() { FullName = "Steve" };
var person02 = new Person() { FullName = "Mr. S" };
var person03 = new Person() { FullName = "Mister Steve" };

person02.FullName_Reference = person01;
person03.FullName_Reference = person02;

person01.FullName = "Mr. Steve Jobs";
// person01.FullName is "Mr. Steve Jobs"
// person02.FullName is "Mr. Steve Jobs"
// person03.FullName is "Mr. Steve Jobs"

Seeking for a more elegant way to do it, I even tried to expose the private field _fullName (through a method returning a ref), but i was unable to set the private field (also through a method) of the other object as a reference to the reference object returned previously. It always just copy its value.

Vitox
  • 3,852
  • 29
  • 30
  • 1
    I think even using a ref field (I don't know whether that exists) wouldn't help you much here, because you would still need to write a condition somewhere, as it is all within the same class. – PMF Mar 04 '22 at 08:44
  • What about using a class `RealPerson` and a subclass `ReferenceToPerson` instead? – PMF Mar 04 '22 at 08:46
  • @PMF I abstracted the problem to the simplest form I could. I actually have 16 (could grow) properties that could reference another properties (of same type) of up to 16 different instances. – Vitox Mar 04 '22 at 08:50
  • @PMF about having to still write a condition, you are absolutely right! I would still need to "handle" the reference object in some way... hum... Thanks, I really didn't realize that! – Vitox Mar 04 '22 at 08:53

2 Answers2

2

I'm not familiar with all of the new features of C# 10.0, but you can achieve the desired behaviour by treating the name as an object in it's own right, and referencing the Name object in each of your Person instances. i.e. just treat it as a composition

public class Name
{
    public string FullName { get; set; }

    public Name(string fullName)
    {
        FullName = fullName;
    }
}

public class Person
{
    public Name Name { get; set; }

    public string FullName => Name.FullName;

    public Person(Name name)
    {
        Name = name;
    }
}

var person01 = new Person(new Name("Steve"));
var person02 = new Person(new Name("Mr. S"));
var person03 = new Person(new Name("Mister Steve"));

person02.Name = person01.Name;
person03.Name = person02.Name;

person01.Name.FullName = "Mr. Steve Jobs";
// person01.FullName is "Mr. Steve Jobs"
// person02.FullName is "Mr. Steve Jobs"
// person03.FullName is "Mr. Steve Jobs"

EDIT: to handle requirement explained in comments

public class Name
{
    public string FullName { get; set; }

    private readonly List<Person> _people = new List<Person>();

    public Name(string fullName)
    {
        FullName = fullName;
    }

    public void Add(Person person)
    {
        _people.Add(person);
    }

    public void UpdateNames(Name name)
    {
        foreach (Person person in _people)
        {
            person.SetName(name);
        }
        _people.Clear();
    }
}

public class Person
{
    private Name _name;
    public Name Name
    {
        get => _name;
        set
        {
            _name.UpdateNames(value);
            _name = value;
        }
    }

    public string FullName => Name.FullName;

    public Person(Name name)
    {
        _name = name;
        _name.Add(this);
    }

    public void SetName(Name name)
    {
        _name = name;
        _name.Add(this);
    }
}
JChristen
  • 588
  • 4
  • 21
  • Agreed. If you want a reference type behavior, use a reference type. `string` is not the correct type for the `Name` property because it doesn't behave like OP wants, so use another type. – Kilazur Mar 04 '22 at 09:09
  • That's very clever! Almost perfect. The problem is that the point is not the type (if it's a reference or value type), as the logic becomes broke if there was a `person04` and I set `person02.Name = person04.Name`. Now `person03` which was explicit connected with `person02`, no longer has any connection with `person02`. – Vitox Mar 04 '22 at 09:58
  • The idea of a `ref` would (hypothetically) be that once it is tied, it would always reference the tied field/property, unless directly overridden. My current approach actually does that. – Vitox Mar 04 '22 at 10:05
  • Please see the edit in my answer for the code update. This is a very weird (and not recommended) requirement, not least because it is not intuitive for something like `person02.Name = person04.Name` to affect the value of `person03.Name`. But you can get the desired outcome by having the `Name` class reference the `Person` instances that use that name. Then when you update `person.Name` you can call into the `Name` instance and update the `Person` instances that use that `Name` to point to the new `Name`. – JChristen Mar 04 '22 at 14:54
  • There is nothing weird about the requirement. The idea is pretty simple: set a Field as a Reference to another Field, **just like a pointer works on C++**, as stated on question title. – Vitox Mar 05 '22 at 16:45
  • Your last proposed approach is even more complex than my approach. I understand that you used a list to keep track of the "dependent" fields of that reference field, but that is unnecessary and undesirable. It is too far from a "pointer" approach. (also, the line `_people.Clear();` would make it stop working as soon as the first update occurs). – Vitox Mar 05 '22 at 16:45
  • I said it is unnecessary because every field has to be connected to only one field, and only get the current value of that field on a access time. And I said that it's undesirable because I could easily "connect" a same field as a reference to two or more fields, what would be completely broken. – Vitox Mar 05 '22 at 16:46
  • Your first approach seems very good (use a reference-type to encapsulate a value-type), it's very close, but still does not fulfill the idea of a pointer/ref field as explained before. – Vitox Mar 05 '22 at 16:46
  • I thank you very much for your time, but the answer of Matteo Umili works exactly as requested, and is simpler than my current approach (which was the intention of this question). Anyway, thank you for the effort! – Vitox Mar 05 '22 at 16:46
1

In my opinion the easiest way to achieve what you want is to use a Func<string> to retrieve reference values:

class Person
{
    private Func<string> _fullName;
    public string FullName { get => _fullName(); set => _fullName = () => value; }

    public void SetFullNameRef(Func<string> value)
    {
        _fullName = value;
    }
}


static void Main(string[] args)
{
    var person01 = new Person() { FullName = "Steve" };
    var person02 = new Person() { FullName = "Mr. S" };
    var person03 = new Person() { FullName = "Mister Steve" };

    person02.SetFullNameRef(() => person01.FullName);
    person03.SetFullNameRef(() => person02.FullName);

    person01.FullName = "Mr. Steve Jobs";

    Console.WriteLine(person01.FullName); // Mr. Steve Jobs
    Console.WriteLine(person02.FullName); // Mr. Steve Jobs
    Console.WriteLine(person03.FullName); // Mr. Steve Jobs
}

Or write it like that:

class Person
{
    private Func<string> _fullName;
    public string FullName { get => _fullName(); set => _fullName = () => value; }
    public Func<string> RefFullName { get => () => _fullName(); set => _fullName = () => value(); }
}


static void Main(string[] args)
{
    var person01 = new Person() { FullName = "Steve" };
    var person02 = new Person() { FullName = "Mr. S" };
    var person03 = new Person() { FullName = "Mister Steve" };

    person02.RefFullName = person01.RefFullName;
    person03.RefFullName = person02.RefFullName;

    person01.FullName = "Mr. Steve Jobs";

    Console.WriteLine(person01.FullName); // Mr. Steve Jobs
    Console.WriteLine(person02.FullName); // Mr. Steve Jobs
    Console.WriteLine(person03.FullName); // Mr. Steve Jobs
}
Matteo Umili
  • 3,412
  • 1
  • 19
  • 31
  • That works perfectly! Is the idea to wrap the object (in this case a string) inside a delegate, so the delegate will point to the object as a reference? – Vitox Mar 05 '22 at 15:32
  • @Vitox Correct. Each time you access it, it gets executed, so, for example, after `person02.RefFullName = person01.RefFullName`, whenever you access `person02.FullName`, you are basically accessing `person01.FullName` getter – Matteo Umili Mar 07 '22 at 08:47