|
I see you do not deepcopy valuetypes, but valuetypes may have fields pointing to (mutable) reference types. In these cases the end result is unfortunately not a deep clone.
|
|
|
|
|
You have rocked in the first article itself. Thank you for your great contribution.
Ashok kumar.
|
|
|
|
|
It also works on classes without a paramaterless constructor if you switch:
Activator.CreateInstance(obj.GetType());
with
object newObj;
if (type.GetConstructor(Type.EmptyTypes) != null)
newObj = Activator.CreateInstance(type);
else
newObj = FormatterServices.GetUninitializedObject(type);
(also switched "obj.GetType()" with "type" since we already have the value in a variable)
|
|
|
|
|
My Code :
long num = 0;
object[,] array;
array = (object[,])Array.Copy((Array)array, new object[13, (int)num + 1 ] , array . Length );
My error :
Error 12 Cannot convert type 'void' to 'object[*,*]'
Please help me…
Thanks so much
|
|
|
|
|
public class ArrayLoop
{
public Array array;
int[] indices;
int currentRank;
int TheK;
bool yieldFlag;
public ArrayLoop(Array a)
{ array = a; indices = new int[array.Rank]; currentRank = 0; }
public IEnumerator GetEnumerator()
{
while (Loop(TheK))
{
yieldFlag = false;
yield return indices;
}
}
public bool Loop(int start = 0)
{
int rank = array.Rank;
for (int k = start; k < array.GetLength(currentRank); k++)
{
indices[currentRank] = k;
if (currentRank == rank - 1)
{
TheK = k + 1;
yieldFlag = true;
return true;
}
else
{
currentRank++;
Loop(0);
if (yieldFlag)
return true;
}
}
for (int i = 0; i < rank; i++)
{
if (indices[i] != array.GetLength(i) - 1)
{
currentRank = i; TheK = indices[i] + 1;
return true;
}
}
return false;
}
}
private static bool Valid(object sourceObj, object destObj)
{
if (sourceObj == null)
return true;
Debug.Assert(ReferenceEquals(sourceObj, destObj) == false);
Type sourceType = sourceObj.GetType();
Type destType = destObj.GetType();
if (sourceType != destType)
return false;
if (sourceType.IsValueType || sourceType == typeof(string))
return true;
if (sourceType.IsArray)
{
int pos1 = sourceType.FullName.IndexOf('[');
Debug.Assert(pos1 >= 0);
int pos2 = sourceType.FullName.IndexOf(']', pos1);
string str1 = sourceType.FullName.Substring(0, pos1);
string str2 = sourceType.FullName.Substring(pos2 + 1);
Type elementType = Type.GetType(str1 + str2);
if (elementType.IsValueType || elementType == typeof(string))
{
return true;
}
var sourceArray = sourceObj as Array;
Array destArray = destObj as Array;
int rank = sourceArray.Rank;
ArrayLoop loop = new ArrayLoop(sourceArray);
foreach (int[] index in loop)
{
if (Valid(sourceArray.GetValue(index), destArray.GetValue(index)) == false)
return false;
}
}
if (sourceType.IsClass)
{
FieldInfo[] sourcefields = sourceType.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
FieldInfo[] destfields = sourceType.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < sourcefields.Length; i++)
{
object sourceFieldValue = sourcefields[i].GetValue(sourceObj);
object destFieldValue = sourcefields[i].GetValue(destObj);
if (Valid(sourceFieldValue, destFieldValue) == false)
return false;
}
return true;
}
else
return false;
}
public static T DeepCopy<T>(T obj)
{
if (obj == null)
throw new ArgumentNullException("Object cannot be null");
Object dest = Process(obj);
if (Valid(obj, dest) == false)
MessageBox.Show("拷贝校验错误!");
return (T)dest;
}
static object Process(object obj)
{
if (obj == null)
return null;
Type type = obj.GetType();
if (type.IsValueType || type == typeof(string))
{
return obj;
}
else if (type.IsArray)
{
int pos1 = type.FullName.IndexOf('[');
Debug.Assert(pos1 >= 0);
int pos2 = type.FullName.IndexOf(']', pos1);
string str1 = type.FullName.Substring(0, pos1);
string str2 = type.FullName.Substring(pos2 + 1);
Type elementType = Type.GetType(str1 + str2);
var array = obj as Array;
if (elementType.IsValueType || elementType == typeof(string))
{
return array.Clone();
}
int rank = array.Rank;
int[] indices = new int[rank];
for (int i = 0; i < rank; i++)
indices[i] = array.GetLength(i);
Array copied = Array.CreateInstance(elementType, indices);
ArrayLoop loop = new ArrayLoop(array);
foreach (int[] index in loop)
{
copied.SetValue(Process(array.GetValue(index)), index);
}
return Convert.ChangeType(copied, obj.GetType());
}
else if (type.IsClass)
{
object toret = Activator.CreateInstance(obj.GetType());
FieldInfo[] fields = type.GetFields(BindingFlags.Public |
BindingFlags.NonPublic | BindingFlags.Instance);
foreach (FieldInfo field in fields)
{
object fieldValue = field.GetValue(obj);
if (fieldValue == null)
continue;
field.SetValue(toret, Process(fieldValue));
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}
|
|
|
|
|
I'm copying some ListOf's with this thing, seems to work really, really well.. Aside from when it threw exception on system object.
The is a System.Drawing.Font object in one of my Lists..
If the object is of Class type, the next line is where it crashes:
Dim newobj As Object = Activator.CreateInstance(obj.GetType())
With error code:
An exception of type 'System.MissingMethodException' occurred in mscorlib.dll but was not handled in user code
Additional information: No parameterless constructor defined for this object.
Since this is a system object i can not edit it to give it a blank "New" Constructor, it is also non-inheritable sooo....
What do i do now??
|
|
|
|
|
Hi. Everything works perfect for me except one thing: what if classes we want to copy are located in different assemblies, not in the one, out DeepCopy method is located?
Yes, Type.GetType method will fail and Array.CreateInstance(elementType, array.Length) will throw exception as elementType cannot be null.
To resolve this, I have modified initial code with current:
if (type.IsArray)
{
string fullTypeName = type.FullName.Replace("[]", string.Empty);
if (type.AssemblyQualifiedName != null)
{
fullTypeName = type.AssemblyQualifiedName.Replace("[]", string.Empty);
}
Type elementType = Type.GetType(fullTypeName,
Assembly.Load,
(assem, name, ignore) => assem == null
? Type.GetType(name, false, ignore)
: assem.GetType(name, false, ignore), true);
var array = (Array)obj;
if (elementType != null)
{
Array copied = Array.CreateInstance(elementType, array.Length);
for (int i = 0; i < array.Length; i++)
{
copied.SetValue(Process(array.GetValue(i)), i);
}
return Convert.ChangeType(copied, obj.GetType());
}
return null;
}
The last return null; can be replaced with any Exception you want.
Hope this will be helpful for anybody.
|
|
|
|
|
When I want to use it I get error
|
|
|
|
|
Dude what's wrong with you?! A vote of 1 and just telling the author it didn't work for you, without any further detail.
How would you like it if someone did that on the small number of basic articles you've written?
Uncool.
|
|
|
|
|
The guy is rating just like he'd do on its iPhone...
|
|
|
|
|
Error is No overload for method ChangeType takes two arguments. Am i missing a reference?
|
|
|
|
|
|
I made a couple mods on it so it will also work with structures and decimals. I also made it an extension method, so you can call it using
obj1 = obj2.DeepCopy()
(I also converted it to VB.net)
<Runtime.CompilerServices.Extension()> Public Function DeepCopy(Of T)(ByVal obj As T) As T
If obj Is Nothing Then Return Nothing
Return DirectCast(DeepCopy(obj, New Dictionary(Of Object, Object)()), T)
End Function
Private Function DeepCopy(ByVal obj As Object, ByVal circular As Dictionary(Of Object, Object)) As Object
If obj Is Nothing Then
Return Nothing
End If
Dim typ As Type = obj.GetType()
If typ.IsPrimitive OrElse typ Is GetType(String) OrElse typ Is GetType(Decimal) Then
Return obj
End If
If circular.ContainsKey(obj) Then
Return circular(obj)
End If
If typ.IsArray Then
Dim typeNoArray As String = typ.FullName.Replace("[]", String.Empty)
Dim elementType As Type = Type.GetType(typeNoArray & ", " & typ.Assembly.FullName)
Dim arr As Array = obj
Dim copied As Array = System.Array.CreateInstance(elementType, arr.Length)
circular(obj) = copied
For i As Integer = 0 To arr.Length - 1
Dim element As Object = arr.GetValue(i)
Dim copy As Object = Nothing
If element IsNot Nothing AndAlso circular.ContainsKey(element) Then
copy = circular(element)
Else
copy = DeepCopy(element, circular)
End If
copied.SetValue(copy, i)
Next
Return copied 'Convert.ChangeType(copied, obj.GetType())
ElseIf typ.IsClass Then 'class
Dim newobj As Object = Activator.CreateInstance(obj.GetType())
circular(obj) = newobj
Dim fields As Reflection.FieldInfo() = typ.GetFields(Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
For Each field As Reflection.FieldInfo In fields
Dim fieldValue As Object = field.GetValue(obj)
If fieldValue Is Nothing Then
Continue For
End If
Dim copy As Object = Nothing
If circular.ContainsKey(fieldValue) Then
copy = circular(fieldValue)
Else
copy = DeepCopy(fieldValue, circular)
End If
field.SetValue(newobj, copy)
Next
Return newobj
ElseIf typ.IsValueType Then 'structure
Dim newobj As System.ValueType = Activator.CreateInstance(obj.GetType())
circular(obj) = newobj
Dim fields As Reflection.FieldInfo() = typ.GetFields(Reflection.BindingFlags.Public Or Reflection.BindingFlags.NonPublic Or Reflection.BindingFlags.Instance)
For Each field As Reflection.FieldInfo In fields
Dim fieldValue As Object = field.GetValue(obj)
If fieldValue Is Nothing Then
Continue For
End If
Dim copy As Object = Nothing
If circular.ContainsKey(fieldValue) Then
copy = circular(fieldValue)
Else
copy = DeepCopy(fieldValue, circular)
End If
field.SetValue(newobj, copy)
Next
Return newobj
Else
Return Nothing
End If
End Function
-- Modified Tuesday, June 21, 2011 5:07 PM
modified on Tuesday, June 21, 2011 5:12 PM
|
|
|
|
|
Thank you for this article. Very nicely written code that works exactly as advertised. I am using your modified code that takes into account circular references. You have got my 5!
I think you should update the main article with your circular reference update!
|
|
|
|
|
Hi,
I Already done some code like yours and one of the important things to handle is Circular References.
For example if you have a tree class like this:
public class Tree
{
public TreeNode _root;
}
public class TreeNode
{
public TreeNode _parent;
public TreeNode _lChild;
public TreeNode _rChild;
public TreeNode(TreeNode parent)
{
_parent = parent;
}
}
When you call the method to a object of Tree your code will start do copy the nodes of the tree.
The big problem is that de root node of a tree is the parent of some child, so, when you start to copy the child of the root, your code will look into _parent field and will try to copy the root node again. And this cycle will continue until a stack overflow.
To solve that problem i used a collection of cloned objects. It was something like this;
public class CloneList
{
private IList _originals;
private IList _clones;
public CloneList()
{
_originals = new ArrayList();
_clones = new ArrayList();
}
public object getCloneForOriginal(object original)
{
if (_originals.Contains(original))
{
return _clones[_originals.IndexOf(original)];
}
else
{
return null;
}
}
}
|
|
|
|
|
Ok. I did an update to handle circular references. Here's the code (I can't find how to edit my article ..)
using System;
using System.Reflection;
using System.Collections.Generic;
namespace HAKGERSoft {
public class HCloner {
public static T DeepCopy<T>(T obj) {
if(obj==null)
throw new ArgumentNullException("Object cannot be null");
return (T)Process(obj,new Dictionary<object,object>(){ });
}
static object Process(object obj,Dictionary<object,object>circular) {
if(obj==null)
return null;
Type type=obj.GetType();
if(type.IsValueType || type==typeof(string)) {
return obj;
}
if(type.IsArray) {
if(circular.ContainsKey(obj))
return circular[obj];
string typeNoArray=type.FullName.Replace("[]",string.Empty);
Type elementType=Type.GetType(typeNoArray+", "+type.Assembly.FullName);
var array=obj as Array;
Array copied=Array.CreateInstance(elementType,array.Length);
circular[obj]=copied;
for(int i=0; i<array.Length; i++) {
object element=array.GetValue(i);
object copy=null;
if(element!=null && circular.ContainsKey(element))
copy=circular[element];
else
copy=Process(element,circular);
copied.SetValue(copy,i);
}
return Convert.ChangeType(copied,obj.GetType());
}
if(type.IsClass) {
if(circular.ContainsKey(obj))
return circular[obj];
object toret=Activator.CreateInstance(obj.GetType());
circular[obj]=toret;
FieldInfo[] fields=type.GetFields(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Instance);
foreach(FieldInfo field in fields) {
object fieldValue=field.GetValue(obj);
if(fieldValue==null)
continue;
object copy=circular.ContainsKey(fieldValue)?circular[fieldValue]: Process(fieldValue,circular);
field.SetValue(toret,copy);
}
return toret;
}
else
throw new ArgumentException("Unknown type");
}
}
}
|
|
|
|
|
Glorious!
this implementation was X20 faster than deep clone using Json.Net serialization
|
|
|
|
|
Took me a few hours but i found the problem
The current code does not handle structs well. Structs are value types BUT they can contain reference types!
this caused 2 different problems
1) objects were not cloned at all, the reference was kept to the original object
2) some objects were cloned to multiple instances
The fix is handling structs like classes:
if ((type.IsValueType && type.IsPrimitive ) || type == typeof(string))
if (type.IsClass || !type.IsPrimitive)
This code is the best FAST solution i found for huge data structures with non serializable cyclic references
|
|
|
|
|
public static T Clone<T>(T pItem)<br />
{<br />
using (MemoryStream stream = new MemoryStream())<br />
{<br />
BinaryFormatter serializer = new BinaryFormatter(null, new StreamingContext(System.Runtime.Serialization.StreamingContextStates.Clone));<br />
serializer.Serialize(stream, pItem);<br />
stream.Position = 0;<br />
return (T)serializer.Deserialize(stream);<br />
}<br />
}
|
|
|
|
|
I know that it can be solved by serializer - but still I wanted to make a version with Reflections.
|
|
|
|
|
It can be used only when the class is marked as Serializable , and in most cases its not.
|
|
|
|
|
Hi
It will be very nice if you add some more clarification i.e how it works ... internal dependencies
Thanks
Md. Marufuzzaman
|
|
|
|
|
Only things I can think of:
1) When copying a class you'll call the default constructor for the class.
1a) You can't create a class that requires arguments to the constructor.
2) Anyone subscribed to events in the old class won't be subscribed to events in the new object. However this may not be desired in some cases anyway.
2a) If the old object was subscribed to any events that weren't initiated by the default constructor the new object won't be subscribed to them.
For #2 I have no idea how to work with event subscriptions in Reflection and for #1 not sure how you could work around that so I'll just say good work for now.
And obviously this won't work with items that access data stored outside the class and expect it to be unique for every class instance, such as Controls which use a native window handle to access the wrapped Windows API control, but that's to be expected.
|
|
|
|
|
Impressive reflection work.
|
|
|
|
|