C #에 일반 객체 목록이 있고 목록을 복제하려고합니다. 목록 내의 항목은 복제 가능하지만 수행 할 수있는 옵션이없는 것 같습니다 list.Clone()
.
이 문제를 해결하는 쉬운 방법이 있습니까?
clone()
정의상 깊은 사본이 아닙니까? C #에서는 =로 포인터를 쉽게 전달할 수 있다고 생각했습니다.
C #에 일반 객체 목록이 있고 목록을 복제하려고합니다. 목록 내의 항목은 복제 가능하지만 수행 할 수있는 옵션이없는 것 같습니다 list.Clone()
.
이 문제를 해결하는 쉬운 방법이 있습니까?
clone()
정의상 깊은 사본이 아닙니까? C #에서는 =로 포인터를 쉽게 전달할 수 있다고 생각했습니다.
답변:
확장 방법을 사용할 수 있습니다.
static class Extensions
{
public static IList<T> Clone<T>(this IList<T> listToClone) where T: ICloneable
{
return listToClone.Select(item => (T)item.Clone()).ToList();
}
}
요소가 값 유형 인 경우 다음을 수행 할 수 있습니다.
List<YourType> newList = new List<YourType>(oldList);
그러나 참조 유형이고 딥 카피를 원한다면 (요소가 올바르게 구현되었다고 가정 ICloneable
) 다음과 같이 할 수 있습니다.
List<ICloneable> oldList = new List<ICloneable>();
List<ICloneable> newList = new List<ICloneable>(oldList.Count);
oldList.ForEach((item) =>
{
newList.Add((ICloneable)item.Clone());
});
분명히 ICloneable
위의 제네릭을 바꾸고 구현하는 요소 유형으로 캐스팅하십시오 ICloneable
.
요소 유형이 지원하지 않지만 ICloneable
복사 생성자가있는 경우 대신 다음을 수행 할 수 있습니다.
List<YourType> oldList = new List<YourType>();
List<YourType> newList = new List<YourType>(oldList.Count);
oldList.ForEach((item)=>
{
newList.Add(new YourType(item));
});
개인적으로, 나는 ICloneable
모든 회원의 깊은 사본을 보장 할 필요가 있기 때문에 피할 것 입니다. 대신, 복사 생성자 또는 이와 같은 팩토리 메소드 YourType.CopyFrom(YourType itemToCopy)
가의 새로운 인스턴스를 반환하도록 제안합니다 YourType
.
이러한 옵션은 메소드 (확장 또는 기타)로 랩핑 할 수 있습니다.
ICloneable
정의 된 시점 에서 정의가 복제가 깊거나 얕 았는지 명시하지 않았기 때문에 개체가 복제 할 때 수행 될 복제 작업 유형을 결정할 수 없다는 것입니다. 즉,의 딥 클론 List<T>
을 수행 ICloneable
하려면 딥 카피인지 확인 하지 않고 수행해야합니다 .
newList.AddRange(oldList.Select(i => i.Clone())
또는 newList.AddRange(oldList.Select(i => new YourType(i)
)
얕은 복사본의 경우 대신 일반 List 클래스의 GetRange 메서드를 사용할 수 있습니다.
List<int> oldList = new List<int>( );
// Populate oldList...
List<int> newList = oldList.GetRange(0, oldList.Count);
인용 : Generics Recipes
List<int> newList = oldList.ToList()
합니다. 같은 효과. 그러나 Arkiliknam의 솔루션은 내 의견으로는 가독성에 가장 좋습니다.
public static object DeepClone(object obj)
{
object objResult = null;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
objResult = bf.Deserialize(ms);
}
return objResult;
}
이것은 C # 및 .NET 2.0으로 수행하는 한 가지 방법입니다. 귀하의 개체는이어야 [Serializable()]
합니다. 목표는 모든 참조를 잃어 버리고 새로운 참조를 만드는 것입니다.
목록을 복제하려면 .ToList ()를 호출하십시오. 얕은 사본을 만듭니다.
Microsoft (R) Roslyn C# Compiler version 2.3.2.62116
Loading context from 'CSharpInteractive.rsp'.
Type "#help" for more information.
> var x = new List<int>() { 3, 4 };
> var y = x.ToList();
> x.Add(5)
> x
List<int>(3) { 3, 4, 5 }
> y
List<int>(2) { 3, 4 }
>
약간 수정 한 후 다음을 복제 할 수도 있습니다.
public static T DeepClone<T>(T obj)
{
T objResult;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
ms.Position = 0;
objResult = (T)bf.Deserialize(ms);
}
return objResult;
}
if (!obj.GetType().IsSerializable) return default(T);
예외를 방지하는 첫 번째 문장으로 추가 할 수 있습니다 . 그리고 확장 방법으로 변경하면 Elvis 연산자를 사용할 수도 있습니다 var b = a?.DeepClone();
( var a = new List<string>() { "a", "b" };
예 : 주어진 ).
에있는 모든 단일 객체의 실제 복제가 필요하지 않은 경우 List<T>
목록을 복제하는 가장 좋은 방법은 이전 목록을 collection 매개 변수로 사용하여 새 목록을 만드는 것입니다.
List<T> myList = ...;
List<T> cloneOfMyList = new List<T>(myList);
myList
삽입 또는 제거와 같은 변경 은 영향을 미치지 cloneOfMyList
않으며 그 반대도 마찬가지입니다.
그러나 두 목록에 포함 된 실제 개체는 여전히 동일합니다.
가치 유형에만 관심이 있다면 ...
그리고 당신은 유형을 알고 있습니다 :
List<int> newList = new List<int>(oldList);
이전에 유형을 모르는 경우 도우미 기능이 필요합니다.
List<T> Clone<T>(IEnumerable<T> oldList)
{
return newList = new List<T>(oldList);
}
그냥 :
List<string> myNewList = Clone(myOldList);
프로젝트에서 Newtonsoft.Json을 이미 참조했고 객체를 직렬화 할 수 있다면 항상 다음을 사용할 수 있습니다.
List<T> newList = JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(listToCopy))
아마도 가장 효율적인 방법은 아니지만 100 ~ 1000 번을 수행하지 않으면 속도 차이를 알지 못할 수도 있습니다.
public static Object CloneType(Object objtype)
{
Object lstfinal = new Object();
using (MemoryStream memStream = new MemoryStream())
{
BinaryFormatter binaryFormatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone));
binaryFormatter.Serialize(memStream, objtype); memStream.Seek(0, SeekOrigin.Begin);
lstfinal = binaryFormatter.Deserialize(memStream);
}
return lstfinal;
}
public List<TEntity> Clone<TEntity>(List<TEntity> o1List) where TEntity : class , new()
{
List<TEntity> retList = new List<TEntity>();
try
{
Type sourceType = typeof(TEntity);
foreach(var o1 in o1List)
{
TEntity o2 = new TEntity();
foreach (PropertyInfo propInfo in (sourceType.GetProperties()))
{
var val = propInfo.GetValue(o1, null);
propInfo.SetValue(o2, val);
}
retList.Add(o2);
}
return retList;
}
catch
{
return retList;
}
}
내 친구 Gregor Martinovic와 저는 JavaScript Serializer를 사용하여이 쉬운 솔루션을 생각해 냈습니다. 클래스를 Serializable로 플래그 지정할 필요가 없으며 Newtonsoft JsonSerializer를 사용한 테스트에서 BinaryFormatter를 사용하는 것보다 훨씬 빠릅니다. 모든 객체에 사용할 수있는 확장 방법.
표준 .NET JavascriptSerializer 옵션 :
public static T DeepCopy<T>(this T value)
{
JavaScriptSerializer js = new JavaScriptSerializer();
string json = js.Serialize(value);
return js.Deserialize<T>(json);
}
Newtonsoft JSON을 사용하는 더 빠른 옵션 :
public static T DeepCopy<T>(this T value)
{
string json = JsonConvert.SerializeObject(value);
return JsonConvert.DeserializeObject<T>(json);
}
아무도 이것을 읽지 않으면 운이 좋을 것입니다 ...하지만 Clone 메소드에서 유형 객체 목록을 반환하지 않기 위해 인터페이스를 만들었습니다.
public interface IMyCloneable<T>
{
T Clone();
}
그런 다음 확장명을 지정했습니다.
public static List<T> Clone<T>(this List<T> listToClone) where T : IMyCloneable<T>
{
return listToClone.Select(item => (T)item.Clone()).ToList();
}
그리고 여기 내 A / V 마킹 소프트웨어의 인터페이스 구현이 있습니다. Clone () 메서드가 VidMark 목록을 반환하도록하고 싶었습니다 (ICloneable 인터페이스에서 메서드가 개체 목록을 반환하도록하려는 경우).
public class VidMark : IMyCloneable<VidMark>
{
public long Beg { get; set; }
public long End { get; set; }
public string Desc { get; set; }
public int Rank { get; set; } = 0;
public VidMark Clone()
{
return (VidMark)this.MemberwiseClone();
}
}
마지막으로 클래스 내 확장의 사용법 :
private List<VidMark> _VidMarks;
private List<VidMark> _UndoVidMarks;
//Other methods instantiate and fill the lists
private void SetUndoVidMarks()
{
_UndoVidMarks = _VidMarks.Clone();
}
그것을 좋아하는 사람? 개선 사항이 있습니까?
을 사용하여 간단히 목록을 배열로 변환 한 다음을 사용하여 배열 ToArray
을 복제 할 수도 있습니다 Array.Clone(...)
. 필요에 따라 Array 클래스에 포함 된 메소드가 요구를 충족시킬 수 있습니다.
확장 방법을 사용할 수 있습니다 :
namespace extension
{
public class ext
{
public static List<double> clone(this List<double> t)
{
List<double> kop = new List<double>();
int x;
for (x = 0; x < t.Count; x++)
{
kop.Add(t[x]);
}
return kop;
}
};
}
값 유형 멤버를 사용하여 모든 오브젝트를 복제 할 수 있습니다. 예를 들어 다음 클래스를 고려하십시오.
public class matrix
{
public List<List<double>> mat;
public int rows,cols;
public matrix clone()
{
// create new object
matrix copy = new matrix();
// firstly I can directly copy rows and cols because they are value types
copy.rows = this.rows;
copy.cols = this.cols;
// but now I can no t directly copy mat because it is not value type so
int x;
// I assume I have clone method for List<double>
for(x=0;x<this.mat.count;x++)
{
copy.mat.Add(this.mat[x].clone());
}
// then mat is cloned
return copy; // and copy of original is returned
}
};
참고 : 복사 (또는 복제)를 변경해도 원래 개체에는 영향을 미치지 않습니다.
IClonable을 구현하지 않는 항목의 ICollection을 변환하는 확장 기능을 직접 만들었습니다.
static class CollectionExtensions
{
public static ICollection<T> Clone<T>(this ICollection<T> listToClone)
{
var array = new T[listToClone.Count];
listToClone.CopyTo(array,0);
return array.ToList();
}
}
automapper를 사용하여 객체를 복사합니다. 방금 하나의 객체를 자체에 매핑하는 매핑을 설정했습니다. 이 작업을 원하는 방식으로 감쌀 수 있습니다.
딥 카피의 경우 ICloneable이 올바른 솔루션이지만 ICloneable 인터페이스 대신 생성자를 사용하여 ICloneable에 대한 비슷한 접근 방식이 있습니다.
public class Student
{
public Student(Student student)
{
FirstName = student.FirstName;
LastName = student.LastName;
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
// wherever you have the list
List<Student> students;
// and then where you want to make a copy
List<Student> copy = students.Select(s => new Student(s)).ToList();
사본을 만들려면 다음 라이브러리가 필요합니다.
using System.Linq
System.Linq 대신 for 루프를 사용할 수도 있지만 Linq는 간결하고 깨끗합니다. 마찬가지로 다른 답변이 제안하고 확장 방법 등을 만들 수 있지만 그렇게 할 필요는 없습니다.
다음 코드는 최소한의 변경으로 목록으로 전송해야합니다.
기본적으로 각 연속 루프마다 더 큰 범위의 새로운 난수를 삽입하여 작동합니다. 이미 동일하거나 더 높은 숫자가 존재하는 경우, 그 임의의 숫자를 1만큼 위로 이동하여 더 큰 범위의 새로운 임의 인덱스로 전송하십시오.
// Example Usage
int[] indexes = getRandomUniqueIndexArray(selectFrom.Length, toSet.Length);
for(int i = 0; i < toSet.Length; i++)
toSet[i] = selectFrom[indexes[i]];
private int[] getRandomUniqueIndexArray(int length, int count)
{
if(count > length || count < 1 || length < 1)
return new int[0];
int[] toReturn = new int[count];
if(count == length)
{
for(int i = 0; i < toReturn.Length; i++) toReturn[i] = i;
return toReturn;
}
Random r = new Random();
int startPos = count - 1;
for(int i = startPos; i >= 0; i--)
{
int index = r.Next(length - i);
for(int j = startPos; j > i; j--)
if(toReturn[j] >= index)
toReturn[j]++;
toReturn[i] = index;
}
return toReturn;
}
또 다른 것은 : 당신은 반사를 사용할 수 있습니다. 이를 올바르게 캐시하면 5.6 초 안에 1,000,000 개의 객체를 복제 할 수 있습니다 (내부 객체의 경우 16.4 초).
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Person
{
...
Job JobDescription
...
}
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
public class Job
{...
}
private static readonly Type stringType = typeof (string);
public static class CopyFactory
{
static readonly Dictionary<Type, PropertyInfo[]> ProperyList = new Dictionary<Type, PropertyInfo[]>();
private static readonly MethodInfo CreateCopyReflectionMethod;
static CopyFactory()
{
CreateCopyReflectionMethod = typeof(CopyFactory).GetMethod("CreateCopyReflection", BindingFlags.Static | BindingFlags.Public);
}
public static T CreateCopyReflection<T>(T source) where T : new()
{
var copyInstance = new T();
var sourceType = typeof(T);
PropertyInfo[] propList;
if (ProperyList.ContainsKey(sourceType))
propList = ProperyList[sourceType];
else
{
propList = sourceType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
ProperyList.Add(sourceType, propList);
}
foreach (var prop in propList)
{
var value = prop.GetValue(source, null);
prop.SetValue(copyInstance,
value != null && prop.PropertyType.IsClass && prop.PropertyType != stringType ? CreateCopyReflectionMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { value }) : value, null);
}
return copyInstance;
}
Watcher 클래스를 사용하여 간단한 방법으로 측정했습니다.
var person = new Person
{
...
};
for (var i = 0; i < 1000000; i++)
{
personList.Add(person);
}
var watcher = new Stopwatch();
watcher.Start();
var copylist = personList.Select(CopyFactory.CreateCopyReflection).ToList();
watcher.Stop();
var elapsed = watcher.Elapsed;
결과 : 내부 인스턴스 PersonInstance-16.4, PersonInstance = null-5.6
CopyFactory는 표현식 사용을 포함하여 수십 가지 테스트를 수행 한 테스트 클래스입니다. 이것을 확장 또는 다른 형태로 다른 형태로 구현할 수 있습니다. 캐싱을 잊지 마십시오.
아직 직렬화를 테스트하지는 않았지만 백만 개의 클래스로 개선하는 데는 의문의 여지가 있습니다. 빠른 protobuf / newton을 시도하겠습니다.
추신 : 읽기 간단 성을 위해 여기서는 자동 속성 만 사용했습니다. FieldInfo로 업데이트하거나 직접 구현할 수 있습니다.
최근 에 DeepClone 기능을 사용 하여 프로토콜 버퍼 직렬 변환기를 즉시 테스트했습니다 . 백만 개의 단순한 물체에서 4.2 초가 걸리지 만 내부 물체의 경우 7.4 초로 이깁니다.
Serializer.DeepClone(personList);
요약 : 수업에 액세스 할 수 없으면 도움이됩니다. 그렇지 않으면 개체의 수에 따라 다릅니다. 최대 10,000 개의 객체 (약간 작을 수도 있음)를 사용할 수 있다고 생각하지만 이보다 많은 경우 프로토콜 버퍼 직렬 변환기의 성능이 향상됩니다.
JSON 시리얼 라이저 및 디시리얼라이저를 사용하여 C #에서 객체를 복제하는 간단한 방법이 있습니다.
확장 클래스를 만들 수 있습니다 :
using Newtonsoft.Json;
static class typeExtensions
{
[Extension()]
public static T jsonCloneObject<T>(T source)
{
string json = JsonConvert.SerializeObject(source);
return JsonConvert.DeserializeObject<T>(json);
}
}
복제하고 반대하려면 :
obj clonedObj = originalObj.jsonCloneObject;