All*_*hua 22 .net c# oop cloning
我一直在为项目创建对象,有些实例我必须为这些对象创建一个深层副本我已经想到了使用C#的内置函数,即MemberwiseClone().困扰我的问题是每当我创建一个新类时,我都必须编写一个类似下面代码的函数来进行浅拷贝.有人请帮助我改进这部分并给我一个更好的浅拷贝比第二行代码.谢谢 :)
SHALLOW COPY:
public static RoomType CreateTwin(RoomType roomType)
{
return (roomType.MemberwiseClone() as RoomType);
}
Run Code Online (Sandbox Code Playgroud)
深度复制:
public static T CreateDeepClone<T>(T source)
{
if (!typeof(T).IsSerializable)
{
throw new ArgumentException("The type must be serializable.", "source");
}
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
IFormatter formatter = new BinaryFormatter();
Stream stream = new MemoryStream();
using (stream)
{
formatter.Serialize(stream, source);
stream.Seek(0, SeekOrigin.Begin);
return (T)formatter.Deserialize(stream);
}
}
Run Code Online (Sandbox Code Playgroud)
sll*_*sll 16
使用MemberwiseClone不是进行深层复制(MSDN)的好选择:
MemberwiseClone方法通过创建新对象,然后将当前对象的非静态字段复制到新对象来创建浅表副本.如果字段是值类型,则执行字段的逐位复制.如果字段是引用类型,则复制引用但不引用引用的对象 ; 因此,原始对象及其克隆引用相同的对象.
这意味着如果克隆对象具有引用类型公共字段或属性,则它们将提供与原始对象的字段/属性相同的内存位置,因此克隆对象中的每个更改都将反映在初始对象中.这不是真正的深层复制品.
您可以使用BinarySerialization创建对象的完全独立的实例,请参阅BinaryFormatter类的 MSDN页面以获取序列化示例.
示例和测试工具:
用于创建给定对象的深层副本的扩展方法:
public static class MemoryUtils
{
/// <summary>
/// Creates a deep copy of a given object instance
/// </summary>
/// <typeparam name="TObject">Type of a given object</typeparam>
/// <param name="instance">Object to be cloned</param>
/// <param name="throwInCaseOfError">
/// A value which indicating whether exception should be thrown in case of
/// error whils clonin</param>
/// <returns>Returns a deep copy of a given object</returns>
/// <remarks>Uses BInarySerialization to create a true deep copy</remarks>
public static TObject DeepCopy<TObject>(this TObject instance, bool throwInCaseOfError)
where TObject : class
{
if (instance == null)
{
throw new ArgumentNullException("instance");
}
TObject clonedInstance = default(TObject);
try
{
using (var stream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(stream, instance);
// reset position to the beginning of the stream so
// deserialize would be able to deserialize an object instance
stream.Position = 0;
clonedInstance = (TObject)binaryFormatter.Deserialize(stream);
}
}
catch (Exception exception)
{
string errorMessage = String.Format(CultureInfo.CurrentCulture,
"Exception Type: {0}, Message: {1}{2}",
exception.GetType(),
exception.Message,
exception.InnerException == null ? String.Empty :
String.Format(CultureInfo.CurrentCulture,
" InnerException Type: {0}, Message: {1}",
exception.InnerException.GetType(),
exception.InnerException.Message));
Debug.WriteLine(errorMessage);
if (throwInCaseOfError)
{
throw;
}
}
return clonedInstance;
}
}
Run Code Online (Sandbox Code Playgroud)
NUnit测试:
public class MemoryUtilsFixture
{
[Test]
public void DeepCopyThrowWhenCopyInstanceOfNonSerializableType()
{
var nonSerializableInstance = new CustomNonSerializableType();
Assert.Throws<SerializationException>(() => nonSerializableInstance.DeepCopy(true));
}
[Test]
public void DeepCopyThrowWhenPassedInNull()
{
object instance = null;
Assert.Throws<ArgumentNullException>(() => instance.DeepCopy(true));
}
[Test]
public void DeepCopyThrowWhenCopyInstanceOfNonSerializableTypeAndErrorsDisabled()
{
var nonSerializableInstance = new CustomNonSerializableType();
object result = null;
Assert.DoesNotThrow(() => result = nonSerializableInstance.DeepCopy(false));
Assert.IsNull(result);
}
[Test]
public void DeepCopyShouldCreateExactAndIndependentCopyOfAnObject()
{
var instance = new CustomSerializableType
{
DateTimeValueType =
DateTime.Now.AddDays(1).AddMilliseconds(123).AddTicks(123),
NumericValueType = 777,
StringValueType = Guid.NewGuid().ToString(),
ReferenceType =
new CustomSerializableType
{
DateTimeValueType = DateTime.Now,
StringValueType = Guid.NewGuid().ToString()
}
};
var deepCopy = instance.DeepCopy(true);
Assert.IsNotNull(deepCopy);
Assert.IsFalse(ReferenceEquals(instance, deepCopy));
Assert.That(instance.NumericValueType == deepCopy.NumericValueType);
Assert.That(instance.DateTimeValueType == deepCopy.DateTimeValueType);
Assert.That(instance.StringValueType == deepCopy.StringValueType);
Assert.IsNotNull(deepCopy.ReferenceType);
Assert.IsFalse(ReferenceEquals(instance.ReferenceType, deepCopy.ReferenceType));
Assert.That(instance.ReferenceType.DateTimeValueType == deepCopy.ReferenceType.DateTimeValueType);
Assert.That(instance.ReferenceType.StringValueType == deepCopy.ReferenceType.StringValueType);
}
[Serializable]
internal sealed class CustomSerializableType
{
public int NumericValueType { get; set; }
public string StringValueType { get; set; }
public DateTime DateTimeValueType { get; set; }
public CustomSerializableType ReferenceType { get; set; }
}
public sealed class CustomNonSerializableType
{
}
}
Run Code Online (Sandbox Code Playgroud)
您也可以使用反射来创建对象的副本,这应该是最快的方法,因为序列化也使用反射.
这里有一些代码(测试过):
public static T DeepClone<T>(this T original, params Object[] args)
{
return original.DeepClone(new Dictionary<Object, Object>(), args);
}
private static T DeepClone<T>(this T original, Dictionary<Object, Object> copies, params Object[] args)
{
T result;
Type t = original.GetType();
Object tmpResult;
// Check if the object already has been copied
if (copies.TryGetValue(original, out tmpResult))
{
return (T)tmpResult;
}
else
{
if (!t.IsArray)
{
/* Create new instance, at this point you pass parameters to
* the constructor if the constructor if there is no default constructor
* or you change it to Activator.CreateInstance<T>() if there is always
* a default constructor */
result = (T)Activator.CreateInstance(t, args);
copies.Add(original, result);
// Maybe you need here some more BindingFlags
foreach (FieldInfo field in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
{
/* You can filter the fields here ( look for attributes and avoid
* unwanted fields ) */
Object fieldValue = field.GetValue(original);
// Check here if the instance should be cloned
Type ft = field.FieldType;
/* You can check here for ft.GetCustomAttributes(typeof(SerializableAttribute), false).Length != 0 to
* avoid types which do not support serialization ( e.g. NetworkStreams ) */
if (fieldValue != null && !ft.IsValueType && ft != typeof(String))
{
fieldValue = fieldValue.DeepClone(copies);
/* Does not support parameters for subobjects nativly, but you can provide them when using
* a delegate to create the objects instead of the Activator. Delegates should not work here
* they need some more love */
}
field.SetValue(result, fieldValue);
}
}
else
{
// Handle arrays here
Array originalArray = (Array)(Object)original;
Array resultArray = (Array)originalArray.Clone();
copies.Add(original, resultArray);
// If the type is not a value type we need to copy each of the elements
if (!t.GetElementType().IsValueType)
{
Int32[] lengths = new Int32[t.GetArrayRank()];
Int32[] indicies = new Int32[lengths.Length];
// Get lengths from original array
for (int i = 0; i < lengths.Length; i++)
{
lengths[i] = resultArray.GetLength(i);
}
Int32 p = lengths.Length - 1;
/* Now we need to iterate though each of the ranks
* we need to keep it generic to support all array ranks */
while (Increment(indicies, lengths, p))
{
Object value = resultArray.GetValue(indicies);
if (value != null)
resultArray.SetValue(value.DeepClone(copies), indicies);
}
}
result = (T)(Object)resultArray;
}
return result;
}
}
private static Boolean Increment(Int32[] indicies, Int32[] lengths, Int32 p)
{
if (p > -1)
{
indicies[p]++;
if (indicies[p] < lengths[p])
{
return true;
}
else
{
if (Increment(indicies, lengths, p - 1))
{
indicies[p] = 0;
return true;
}
else
{
return false;
}
}
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
添加了一些代码,现在您可以使用该方法复制复杂对象(甚至是具有多个维度的数组).请注意,代表仍未实现.
如果你想要一个完整的实现,你需要处理这个ISerializable
并不是很难但需要一些时间来反映现有代码的接口.这是一次用于远程实现吗?