development

C #에서 일반 목록을 어떻게 복제합니까?

big-blog 2020. 2. 10. 22:20
반응형

C #에서 일반 목록을 어떻게 복제합니까?


C #에 일반 객체 목록이 있고 목록을 복제하려고합니다. 목록의 항목은 복제 가능하지만 수행 할 수있는 옵션이없는 것 같습니다 list.Clone().

이 문제를 해결하는 쉬운 방법이 있습니까?


확장 방법을 사용할 수 있습니다.

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.

이러한 옵션은 메소드 (확장 또는 기타)로 랩핑 할 수 있습니다.


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()]합니다. 목표는 모든 참조를 잃어 버리고 새로운 참조를 만드는 것입니다.


얕은 복사본의 경우 대신 일반 List 클래스의 GetRange 메서드를 사용할 수 있습니다.

List<int> oldList = new List<int>( );
// Populate oldList...

List<int> newList = oldList.GetRange(0, oldList.Count);

인용 : Generics Recipes


약간 수정 한 후 다음을 복제 할 수도 있습니다.

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;
}

목록을 복제하려면 .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 }
> 

에있는 모든 단일 객체의 실제 복제가 필요하지 않은 경우 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);

AutoMapper (또는 원하는 매핑 라이브러리)를 사용하여 복제하는 것은 간단하고 유지 관리가 쉽습니다.

맵핑을 정의하십시오.

Mapper.CreateMap<YourType, YourType>();

마술을하십시오 :

YourTypeList.ConvertAll(Mapper.Map<YourType, YourType>);

프로젝트에서 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 class CloneableList<T> : List<T>, ICloneable where T : ICloneable
{
  public object Clone()
  {
    var clone = new List<T>();
    ForEach(item => clone.Add((T)item.Clone()));
    return clone;
  }
}

    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;
        }
    }

을 사용하여 간단히 목록을 배열로 변환 한 다음을 사용하여 배열 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 
    }
};

참고 : 복사 (또는 복제)를 변경해도 원래 개체에는 영향을 미치지 않습니다.


동일한 용량의 복제 된 목록이 필요한 경우 다음을 시도해보십시오.

public static List<T> Clone<T>(this List<T> oldList)
{
    var newList = new List<T>(oldList.Capacity);
    newList.AddRange(oldList);
    return newList;
}

내 친구 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);
}

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를 사용하여 객체를 복사합니다. 방금 하나의 객체를 자체에 매핑하는 매핑을 설정했습니다. 이 작업을 원하는 방식으로 감쌀 수 있습니다.

http://automapper.codeplex.com/


 //try this
 List<string> ListCopy= new List<string>(OldList);
 //or try
 List<T> ListCopy=OldList.ToList();

아무도 이것을 읽지 않으면 운이 좋을 것입니다 ...하지만 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();
}

그것을 좋아하는 사람? 개선 사항이 있습니까?


이 경우 캐스트를 사용하면 얕은 사본에 도움이 될 수 있습니다.

IList CloneList(IList list)
{
    IList result;
    result = (IList)Activator.CreateInstance(list.GetType());
    foreach (object item in list) result.Add(item);
    return result;
}

일반 목록에 적용 :

List<T> Clone<T>(List<T> argument) => (List<T>)CloneList(argument);

다음 코드는 최소한의 변경으로 목록으로 전송해야합니다.

기본적으로 각 연속 루프마다 더 큰 범위에서 새로운 난수를 삽입하여 작동합니다. 이미 동일하거나 높은 숫자가있는 경우, 임의의 숫자를 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;

딥 카피의 경우 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는 간결하고 깨끗합니다. 마찬가지로 다른 답변이 제안하고 확장 방법 등을 만들 수 있지만 그렇게 할 필요는 없습니다.

참고 URL : https://stackoverflow.com/questions/222598/how-do-i-clone-a-generic-list-in-c



반응형