0
\$\begingroup\$

I've found a good example of how to do this in Java and how you can get the same behavior in c#, so I've made made an attempt at it:

public class CircularReferenceOne
{
    public int Identifier;

    public CircularReferenceTwo CR2;

    public CircularReferenceOne HCopy()
    {
        var track = new Dictionary<int, object>();
        return this.HCircCopy(this, track);
    }

    public CircularReferenceOne HCircCopy(object obj, Dictionary<int, object> track)
    {
        object clone;
        if (track.TryGetValue(RuntimeHelpers.GetHashCode(obj), out clone))
        {
            var c = clone as CircularReferenceOne;
            if (null != c)
                return c;
        }

        var myClone = new CircularReferenceOne();
        track.Add(RuntimeHelpers.GetHashCode(obj), myClone);

        myClone.Identifier = this.Identifier;
        CircularReferenceTwo cr = this.CR2;
        myClone.CR2 = null == cr ? null : cr.HCircCopy(cr, track);

        return myClone;
    }
}

Note: CircularReferenceTwo is identical to CircularReferenceOne.

Can anyone can see any potential limitations with this approach? If so, do you know of a better way to do this?

Keep in mind that I'd like the approach to be as performant as possible (memory/cpu), even if readability is sacrificed a bit.

Note: The goal is to find the general algorithm for performing a deep-copy by hand in the most efficient way possible, so using things like binary serialization are out of the question.

Edit Updated version based on @svick's answer:

public class CircularReferenceOne
{
    public int Identifier;

    public CircularReferenceTwo CR2;

    public CircularReferenceOne HCopy()
    {
        var track = new Dictionary<object, object>(ObjectReferenceComparer.Instance);
        return this.HCircCopy(track);
    }

    public CircularReferenceOne HCircCopy(Dictionary<object, object> track)
    {
        object clone;
        if (track.TryGetValue(this, out clone))
        {
            var c = clone as CircularReferenceOne;
            if (null != c)
                return c;
        }

        var myClone = new CircularReferenceOne();
        track.Add(this, myClone);

        myClone.Identifier = this.Identifier;
        CircularReferenceTwo cr = this.CR2;
        myClone.CR2 = null == cr ? null : cr.HCircCopy(track);

        return myClone;
    }
}

Here's the implementation for ObjectReferenceComparer:

public class ObjectReferenceComparer : EqualityComparer<object>
{
    public static readonly ObjectReferenceComparer Instance =
        new ObjectReferenceComparer();

    public override bool Equals(object first, object second)
    {
        return object.ReferenceEquals(first, second);
    }

    public override int GetHashCode(object obj)
    {
        return RuntimeHelpers.GetHashCode(obj);
    }
}

Update I came across this StackOverflow answer, and after reading Ani's answer I decided to actually check out the implementation for System.Dynamic.Utils.ReferenceEqualityComparer<T> which I've added here for future reference:

// Type: System.Dynamic.Utils.ReferenceEqualityComparer`1
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll

using System.Collections.Generic;
using System.Runtime.CompilerServices;

namespace System.Dynamic.Utils
{
    internal sealed class ReferenceEqualityComparer<T> : IEqualityComparer<T>
    {
        internal static readonly ReferenceEqualityComparer<T> Instance =
            new ReferenceEqualityComparer<T>();

        static ReferenceEqualityComparer()
        {
        }

        private ReferenceEqualityComparer()
        {
        }

        public bool Equals(T x, T y)
        {
            return object.ReferenceEquals((object) x, (object) y);
        }

        public int GetHashCode(T obj)
        {
            return RuntimeHelpers.GetHashCode((object) obj);
        }
    }
}

Now I'm much more comfortable with this implementation!

\$\endgroup\$
2
\$\begingroup\$

You should never use hash codes like this, because they are not unique. Two different objects can have the same hash code and when that happens, your algorithm breaks down. Instead you should use an implementation of IEqualityComparer that uses object.ReferenceEquals() and RuntimeHelpers.GetHashCode().

Also, I don't understand why does HCircCopy() have the obj parameter, when it's always going to be this.

\$\endgroup\$
3
  • \$\begingroup\$ How does the revision look? \$\endgroup\$ Apr 22 '13 at 1:51
  • 1
    \$\begingroup\$ @JesseBuesking Yeah, that's much better. One more thing I noticed: in C#, it's not necessary to use Yoda conditions, just write if (c != null). \$\endgroup\$
    – svick
    Apr 22 '13 at 2:17
  • \$\begingroup\$ yeah, force of habit :D \$\endgroup\$ Apr 22 '13 at 2:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.