- Home /
Copy one Generic List to another one
Weird question. I would like to copy a generic list from script A to another generic list from script B, but I would like to do this in another script "C"? Any thoughts? I cant seem to get anything working in Script C.
Both generic list have the same variable inside the public class, and variable range from strings, ints, floats, gameobjects, and transforms. I am thinking of doing it manually for each variable inside the generic lists, but I am trying to see if there is a faster way of doing it?
Thanks: James
Answer by Cherno · Feb 17, 2016 at 09:22 PM
If you just want to have both scripts referencing the same list, you can simple use
scriptB.list = scriptA.list;
However, if you want to actually copy the list, I'd use his handy helper class I found online:
using System;
using System.Reflection;//for copying objects
using System.Linq;//for string[].Contains()
using UnityEngine;//for print
public class CopyData {
/// <summary>
/// Copies the data of one object to another. The target object gets properties of the first.
/// Any matching properties (by name) are written to the target.
/// </summary>
/// <param name="source">The source object to copy from</param>
/// <param name="target">The target object to copy to</param>
public static void CopyObjectData(object source, object target)
{
CopyObjectData(source, target, String.Empty, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
}
/// <summary>
/// Copies the data of one object to another. The target object gets properties of the first.
/// Any matching properties (by name) are written to the target.
/// </summary>
/// <param name="source">The source object to copy from</param>
/// <param name="target">The target object to copy to</param>
/// <param name="excludedProperties">A comma delimited list of properties that should not be copied</param>
/// <param name="memberAccess">Reflection binding access</param>
public static void CopyObjectData(object source, object target, string excludedProperties, BindingFlags memberAccess)
{
string[] excluded = null;
if (!string.IsNullOrEmpty(excludedProperties))
{
excluded = excludedProperties.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
MemberInfo[] miT = target.GetType().GetMembers(memberAccess);
foreach (MemberInfo Field in miT)
{
string name = Field.Name;
// Skip over excluded properties
if (string.IsNullOrEmpty(excludedProperties) == false
&& excluded.Contains(name))
{
continue;
}
if (Field.MemberType == MemberTypes.Field)
{
FieldInfo sourcefield = source.GetType().GetField(name);
if (sourcefield == null) { continue; }
object SourceValue = sourcefield.GetValue(source);
((FieldInfo)Field).SetValue(target, SourceValue);
}
else if (Field.MemberType == MemberTypes.Property)
{
PropertyInfo piTarget = Field as PropertyInfo;
PropertyInfo sourceField = source.GetType().GetProperty(name, memberAccess);
if (sourceField == null) { continue; }
if (piTarget.CanWrite && sourceField.CanRead)
{
object targetValue = piTarget.GetValue(target, null);
object sourceValue = sourceField.GetValue(source, null);
if (sourceValue == null) { continue; }
if (sourceField.PropertyType.IsArray
&& piTarget.PropertyType.IsArray
&& sourceValue != null )
{
CopyArray(source, target, memberAccess, piTarget, sourceField, sourceValue);
}
else
{
CopySingleData(source, target, memberAccess, piTarget, sourceField, targetValue, sourceValue);
}
}
}
}
}
private static void CopySingleData(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object targetValue, object sourceValue)
{
//instantiate target if needed
if (targetValue == null
&& piTarget.PropertyType.IsValueType == false
&& piTarget.PropertyType != typeof(string))
{
if (piTarget.PropertyType.IsArray)
{
targetValue = Activator.CreateInstance(piTarget.PropertyType.GetElementType());
}
else
{
targetValue = Activator.CreateInstance(piTarget.PropertyType);
}
}
if (piTarget.PropertyType.IsValueType == false
&& piTarget.PropertyType != typeof(string))
{
CopyObjectData(sourceValue, targetValue, "", memberAccess);
piTarget.SetValue(target, targetValue, null);
}
else
{
if (piTarget.PropertyType.FullName == sourceField.PropertyType.FullName)
{
object tempSourceValue = sourceField.GetValue(source, null);
piTarget.SetValue(target, tempSourceValue, null);
}
else
{
CopyObjectData(piTarget, target, "", memberAccess);
}
}
}
private static void CopyArray(object source, object target, BindingFlags memberAccess, PropertyInfo piTarget, PropertyInfo sourceField, object sourceValue)
{
int sourceLength = (int)sourceValue.GetType().InvokeMember("Length", BindingFlags.GetProperty, null, sourceValue, null);
Array targetArray = Array.CreateInstance(piTarget.PropertyType.GetElementType(), sourceLength);
Array array = (Array)sourceField.GetValue(source, null);
for (int i = 0; i < array.Length; i++)
{
object o = array.GetValue(i);
object tempTarget = Activator.CreateInstance(piTarget.PropertyType.GetElementType());
CopyObjectData(o, tempTarget, "", memberAccess);
targetArray.SetValue(tempTarget, i);
}
piTarget.SetValue(target, targetArray, null);
}
}
It can be used for virtually anything that needs to be coöied (as opposed to jsut a reference). It doesn't matter if it's a single variable, collection, or a whole script/class instance.
For you case, you would then use
CopyData.CopyObjectData(scriptA.list, scriptB.list);
I dont even know where to begin. I was trying to find a simple way of doing it, like listA = listB. I think ill just transfer the values manually. Thanks for your time tho.
I have been bashing my head against a wall for the last 10 hours trying to find a concise way to do this.
You are a life saver!
Please do not ever use this method on a generic List. This method does not perform a deep copy but a shallow copy. That means it literally just copies the values of the direct members of the provided object to the destination object. Doing this on a generic List you will create a zombie twin List. That means both of your list instances will use the same internal array after that copy.
Interestingly the method does properly create a new array if it encounters a property of an array type. However fields are not treated this way. Since the default binding flags do include nonpublic members as well the result is not than dangerous.
Answer by Bunny83 · Jan 12, 2021 at 05:12 AM
If you want to copy a list from one script to another there are generally 3 different ways.
First of all if you are happy that both scripts will actually use the same list, you can simply assign the list of script1 to the list varable of script2. That way both scripts will be using the same List instance. So if script1 adds an element to the list, script2 will see that element as well.
The other two ways are about actually copying the content of the list. The first usual way is to simply create a new list and provide the source list from the other script as parameter to the constructor. This will copy the content of the source list into the newly created list.
If you want to do this copying more frequence and you want to avoid creating a new list all the time (to prevent garbage generation), the general approach is to first call Clear() on the target list to make it empty and then use AddRange and provide the source list.
To follow the code example of Cherno, here are the 3 solutions:
// both scripts will use the list of scriptA after that line
scriptB.list = scriptA.list;
// we just create a new list and fill it with the same content of the other one
scriptB.list = new List<YourElementType>(scriptA.list);
// This is the garbage friendly version
scriptB.list.Clear();
scriptB.list.AddRange(scriptA.list);