blah blah blah is here! blah blah » Close

up1down
link

Suppose I have this class:

public class MyClass
{
public int ID;
public string Name;
private string Buffer;
public string DontCopyThisData;
public string GetIDAndName() {
return ID.ToString() + ":" + Name;
}

public MyClass() {
ID = 0;
Name = "Undefined";
}
}


Now I want to design a constructor that will take one instance of this class as its parameter and copy only specific items to the new instance:

public MyClass(MyClass template) {
ID = template.ID;
Name = template.Name;
}


That obviously works, but two caveats:

1. Once you end up with more than a couple of instance variables, you'll end up with a long string of copy statements, plus if you ever modify the class, you'll have to remember to go edit that constructor and add the new variable.

2. I don't know if there's a way to copy *all* instance variables, but if there is, that still wouldn't enable you to exclude certain variables as shown in the example.

3. How to handle property set/gets? For example, some variables may be deliberately private, and exposed only via a set accessor. This obviously works if doing it manually, but if an automated method is to be used, it won't work to simply copy over raw instance variables, unless...

4. How to handle private variables? Since we're passing a reference to an existing class, we can't access its private variables, meaning that we can't copy those!

Can anyone shed some light on this whole scenario?
Thanks

fm

last answered 8 months ago

1 answers

up2down
link

Taking your points in the same order:

1. Agreed.

2. The only way to automatically copy all instance fields is to use reflection. However, you'd need to specify the names of any fields to exclude.

3 & 4. Copying private fields is no problem whether you do it manually or use reflection.

In the former case, MyClass still has access to the private fields of any other MyClass objects passed in as method arguments. In the latter case, as long as there are no 'permissions' problems, you just need to specify BindingFlags.NonPublic.

Here's an example to illustrate these points:

using System;
using System.Reflection;

public class MyClass
{
public int ID;
public string Name;
private string buffer;

public string Buffer
{
get { return buffer; }
set { buffer = value; }
}

public string DontCopyThisData = "Jack";
public string GetIDAndName() {
return ID.ToString() + ":" + Name;
}

public MyClass() {
ID = 0;
Name = "Undefined";
}

public MyClass(MyClass template) {
ID = template.ID;
Name = template.Name;
buffer = template.buffer; // no compiler error
}

public MyClass(MyClass template, string[] exclusions) {
Type t = this.GetType();
BindingFlags bf = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
FieldInfo[] fia = t.GetFields(bf);
foreach(FieldInfo fi in fia)
{
if (exclusions == null || Array.IndexOf(exclusions, fi.Name) == -1) // Edited this line (see comment below)
{
fi.SetValue(this, fi.GetValue(template));
}
}
}
}

class Test
{
static void Main()
{
MyClass mc = new MyClass();
mc.ID = 1;
mc.Name = "Fred";
mc.Buffer = "abcd";
mc.DontCopyThisData = "Dave";
MyClass mc2 = new MyClass(mc, new string[]{"DontCopyThisData"});
Console.WriteLine(mc2.ID); // 1
Console.WriteLine(mc2.Name); // Fred
Console.WriteLine(mc2.Buffer); // abcd
Console.WriteLine(mc2.DontCopyThisData); // Jack (not Dave)
Console.ReadKey();
}
}


EDIT

I've just thought of another way that you could exclude fields to be copied using a custom attribute:
using System;
using System.Reflection;

[AttributeUsage(AttributeTargets.Field)]
public class CopyExcludeAttribute : Attribute
{
}

public class MyClass
{
public int ID;
public string Name;
private string buffer;

public string Buffer
{
get { return buffer; }
set { buffer = value; }
}

[CopyExclude]
public string DontCopyThisData = "Jack";

public string GetIDAndName() {
return ID.ToString() + ":" + Name;
}

public MyClass() {
ID = 0;
Name = "Undefined";
}


public MyClass(MyClass template) {
Type t = this.GetType();
BindingFlags bf = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
FieldInfo[] fia = t.GetFields(bf);
foreach(FieldInfo fi in fia)
{
if (fi.GetCustomAttributes(typeof(CopyExcludeAttribute), true).Length == 0)
{
fi.SetValue(this, fi.GetValue(template));
}
}
}
}

class Test
{
static void Main()
{
MyClass mc = new MyClass();
mc.ID = 1;
mc.Name = "Fred";
mc.Buffer = "abcd";
mc.DontCopyThisData = "Dave";
MyClass mc2 = new MyClass(mc);
Console.WriteLine(mc2.ID); // 1
Console.WriteLine(mc2.Name); // Fred
Console.WriteLine(mc2.Buffer); // abcd
Console.WriteLine(mc2.DontCopyThisData); // Jack (not Dave)
Console.ReadKey();
}
}

fmillion
460

I did not know you could access private fields that way. I'll give that a try. For this particular project I only have to copy 12 fields, so I may just stick with a manual copy, however the reflection idea is a good reusable class that I may experiment with for a future project in which I may need to copy larger amounts of fields between classes. thanks

vulpes
12813

Yes, the private access point is quite surprising and I suspect the C# design team allowed it to facilitate the 'copy constructor' scenario we have here. Just noticed a bug in my first version which would mean having to pass an empty array (rather than just null) when there were no exclusions. I've corrected it now :)

Feedback