1

So I'm working with code in which there are two object that contain each other, inside a list of which I do not know the contents of until runtime. I have a function that needs to convert the first object to a third object, but to do such I need to convert the second object aswell, however to do that I will need the convert the first, and here is the chicken and the egg problem.

Problem Example

namespace chicken // Also I like formatting my code like this, so don't judge me
{
    public class Object1 { // Object One and Two are of the same class
        public List<dynamic> contents = new List<dynamic>();

        public Object1() {}
        public Object1(List<dynamic> contents) {
            this.contents = contents;
        }
    }

    public class Object3 {
        public string name;
        public Object3 friend;
        public string pet;

        public Object3(List<dynamic> converted) {
            this.name = converted[0];
            this.friend = converted[1];
            this.pet = converted[2];
        }
    }

    public class Program {
        public static void Main(string[] args) {
            Object1 object1 = new Object1(); // Just to create the problem, they don't
            Object1 object2 = new Object1(); // get created like this in the actual code

            object1.contents = new List<dynamic> {
                "Steve Johnson", // This is example data, this order is supposed to be unknown
                object2,
                "Biscut",
            };

            object2.contents = new List<dynamic> {
                "Bob Smith",
                object1,
                "Tiny",
            };

            Object3 final = convert(object1); // Runs the conversion function
        }

        public static Object3 convert(Object1 obj) {
            List<dynamic> converted = new List<dynamic>(); // I need a sanitized list for Object3
            for (int i = 0; i < obj.contents.Count; i++) {
                if (obj.contents[i] is Object1) {
                    converted.Add(convert(obj.contents[i])); // Causes infinite loop due to chicken and egg problem
                    continue;
                } converted.Add(obj.contents[i]);
            }
            
            Object3 object3 = new Object3(converted); // Again the list order is unknown
            return object3;
        }
    }
}

I've tried using references, where there is a tunnel object and 'Object3' passes references to it's varibles to it, so I can semi construct Object3, put it in a cache, pass it to object 2 to convert it, then put in the values though the tunnel object containing the references. This got to complicated and I honestly don't know what to do without having an empty constuctor for Object 3.

GreenChild
  • 13
  • 3
  • @starball It's as compact as I can make it, while retaining the specific nuisances of this chicken and egg problem. – GreenChild Jan 02 '23 at 23:16
  • 1
    c# compiler is perfectly happy with class A containing an instance of class B and class B containing an instance of class A – pm100 Jan 02 '23 at 23:23
  • 2
    Why are you passing in a list of dynamic objects to the constructors instead of strongly typed parameters? – juharr Jan 02 '23 at 23:34
  • So you're trying to figure out how to convert everything of one type into another type in an object graph that can have cyclic references? I think the approach you began trying is a good one. You never mentioned why Object3 cannot have an empty constructor. How firm is that requirement? – StriplingWarrior Jan 02 '23 at 23:34
  • What you need to do is keep track of all `Object1` references that you have already converted and once you hit one that you've already converted stop. – juharr Jan 02 '23 at 23:40
  • 1
    It's exceedingly horrible to use `dynamic` like this. There are a few occasions where `dynamic` is useful, but this isn't one. This isn't a "Chicken and Egg" problem. It's a "Chicken and Chicken" problem. You have purposely taken two chicken objects and have their contents refer to each other - and you then recursively navigate the chickens. It has nothing to do with an egg. – Enigmativity Jan 03 '23 at 01:16
  • @Enigmativity Well then, please provide an alternative that avoids the usage of dynamic – GreenChild Jan 03 '23 at 12:58
  • @juharr due to my specific circumstance, the parameters are unknown, the types are unknown, all that is known is a stream of objects of many different types that I need to pass into the constructor, so I have chosen to order them within a dynamic list – GreenChild Jan 03 '23 at 13:00
  • What happens if these unknown parameters do not match your very much known properties? – juharr Jan 03 '23 at 13:48
  • @GreenChild - You need to explain what is you actually want because your code doesn't currently describe it. Please see my answer below as to why. – Enigmativity Jan 03 '23 at 21:19
  • 1
    @GreenChild - "I have chosen to order them within a dynamic list" - so a `List` would probably suffice too and it would keep you safer. However, it sounds like you're creating some sort of dependency injection code. That's a well solved problem that doesn't require list of objects as part of your objects. Again, you need to describe what you actually want for me to suggest anything further. – Enigmativity Jan 03 '23 at 21:32

2 Answers2

1

The problem you're describing can be simplified down to this:

In the following code, how can we make it so Node a references b and b references a?

Node a, b;
a = new Node(new object[] { b });
b = new Node(new object[] { a });

public record Node(IEnumerable<object> Links);

You already hit on the solution: allow a Node to be constructed without all of its possible objects, so you can construct the Nodes first, and then insert them where they belong. It sounds like you don't want to give Node ("Object3") a default constructor, but the way you've implemented it at the moment, it should still be possible to add values after the fact, if you can add to its items list after its construction.

List<object> aList = new(), bList = new();
Node a = new Node(aList), b = new Node(bList);
aList.Add(b);
bList.Add(a);

If that will work for you, then the rest is just the details you've described:

using references, where there is a tunnel object and 'Object3' passes references to its variables to it, so I can semi construct Object3, put it in a cache, pass it to object 2 to convert it, then put in the values though the tunnel object containing the references.

It may be complicated, but that's pretty much what has to happen.

If, for some reason, you need your Object3 class structure to be immutable, so you cannot change its contents after its construction, then you're at an impasse: your requirements are clearly impossible. You're defining an object that must be constructed with all its dependencies, but its dependencies need it to be constructed before they can be constructed.

StriplingWarrior
  • 151,543
  • 27
  • 246
  • 315
0

Here is a minimal representation of your code:

namespace chicken
{
    public class Program
    {
        public static void Main(string[] args)
        {
            List<dynamic> chicken1 = new List<dynamic> { "Steve Johnson", null, "Biscut", };
            List<dynamic> chicken2 = new List<dynamic> { "Bob Smith", chicken1, "Tiny", };
            chicken1[1] = chicken2;
            Convert(chicken1);
        }

        public static void Convert(List<dynamic> chicken)
        {
            foreach (dynamic inner in chicken)
            {
                if (inner is List<dynamic>)
                {
                    Convert(inner);
                }
            }
        }
    }
}

You've just created two dynamic lists that refer to each other and then you try to recursively navigate from one list to the other infinitely.

There is nothing about an egg is your scenario that causes your problem. And, there's very little to do with chickens either, as you really only have two dynamic lists.

I suspect you have a real-world example that may have a chicken versus egg problem, but you haven't translated it into your question.

The bottom-line for me is that there are very few good uses for the keyword dynamic. In 99% of cases it's just syntactic sugar for adding bugs in your code.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172