How to assign a reference to a class member to another class member?

S

Steve Goldman

Even asking this question probably demonstrates that I have a
fundamental misunderstanding of how values and references work in C#,
but here goes:

I'd like to assign a reference to an arbitrary class member to another
class member, so that I can later mutate the stored reference. I'd like
to use this for an undo function - basically, when you make a change to
a class member value, you call SaveMemberValue() to store the reference
and original value. Later, if necessary, you can Restore() the original
value without knowing at compile time which member was changed.

The illegal code below illustrates what I'm trying to accomplish.
Another approach that would work in my application is to have separate
Restore() functions for each class member, but I think that's ugly.

I'd appreciate any help with this. Thanks in advance.



using System;


class RestoreTest
{
private int a;
private int b;
private int c;
private int savedValue;
//ReferenceToInt is a bogus type
private ReferenceToInt savedValueReference;

public static void Main(string[] args)
{
RestoreTest r = new RestoreTest();
r.a=1;
r.b=2;
r.c=3;
Console.WriteLine("r's values are {0}, {1}, and {2}",
r.a,r.b,r.c);
//Output: r's values are 1, 2, and 3
r.Mutate(ref r.a,6);
Console.WriteLine("r's values are {0}, {1}, and {2}",
r.a,r.b,r.c);
//Output: r's values are 6, 2, and 3
r.Restore();
Console.WriteLine("r's values are {0}, {1}, and {2}",
r.a,r.b,r.c);
//Output: r's values are 1, 2, and 3
}
private void Mutate(ref int member, int val)
{
member=val;
SaveMemberValue((referenceToInt)member,val);
}
private void SaveMemberValue(ReferenceToInt referenceToMember,
int val)
{
savedValueReference=referenceToMember;
savedValue=val;
}
private void Restore()
{
savedValueReference=savedValue;
}
}
 
M

Marc Gravell

Well, yup; that won't really work "as is".

Various options available:
1: use a basic Clone() approach to copy the class, and keep that as a
baseline (probably inside the class), then Undo() can copy back. Needs
maintenance each time you add a property.

2: use a common structure (such a Dictionary<string [name], object
[value]> to keep all old values, and a common update method to change
them - similar to standard property notifications, and use reflection
to do the undo. Involves boxing and reflection, but little maintenance.
A crude example follows the body

3: use a property-bag approach to store each property in a class; uses
more memory and slightly slower (no direct field access), but can (done
correctly and using generics) avoid boxing and reflection, as the
generic property-bag-value could hold the old value typed as T. Also
more complex.

4: don't DataSets support something like this out-of-the-box? Can't be
sure, as I don't make a habit of using them... but...

in any event, you should probably hook the actual Mutate/Restore,
Commit/Undo, etc (whatever you call them) into the IEditableObject
interface? Could also try looking for classes that implement this
interface, and see how they do it? Reflector only shows DataRowView in
the standard classes, but may be more? Or maybe not: IIRC the main need
for this was to help support data-grid editing, but in 2.0 and
DataGridView I have good reason to believe it works even without
impementing IEditableObject.

Best of luck,

Marc

// CRUDE example of a snapsot hashtable
public int SomeProperty {
get {return someField;}
set {UpdateField("SomeProperty", ref someField, value);}
// note: could use field name, but property name allows easy
extension
// to e.g INotifyPropertyChange
}
public Dictionary<string,object> oldVals = new ...
// note generics here to simpify comparer
private bool UpdateField<T>(string propertyName, ref T field, T value)
{
if(EqualityComparer<T>.Default.Equals(field,value)) return false; //
no change
if(!oldVals.ContainsKey(propertyName)) { // not captured yet
oldVals.Add(propertyName, field); // snapshot old value
}
field = value;
// possibly also do OnPropertyChanged here if supports event
notifications
return true; // tell the caller something changed
}
public void Commit() {
oldVals.Clear();
}
public void Undo() {
// loop through dictionay,
GetType().GetProperty(propName).SetValue(...);
// TODO
// and finally clear dictionary
Commit();
}
 
R

Robbe Morris [C# MVP]

In addition to other suggestions, this may also be of use to you.
It is a code sample that demonstrates how to copy properties
of a class to another class of the same or different type.
When it finds a property of the same name and type it
copies the value. If not, it ignores it. Very forgiving.

The sample mentions keeping a static version of the
PropertyInfo[] array output to enable relatively
solid performance in regards to speed.

Not sure if this is the "best" fit for you but might
offer a good solution.

http://www.eggheadcafe.com/tutorial...d78-ff541dfbcb56/net-reflection--copy-cl.aspx

--
Robbe Morris - 2004-2006 Microsoft MVP C#
I've mapped the database to .NET class properties and methods to
implement an multi-layered object oriented environment for your
data access layer. Thus, you should rarely ever have to type the words
SqlCommand, SqlDataAdapter, or SqlConnection again.
http://www.eggheadcafe.com/articles/adonet_source_code_generator.asp





Steve Goldman said:
Even asking this question probably demonstrates that I have a fundamental
misunderstanding of how values and references work in C#, but here goes:

I'd like to assign a reference to an arbitrary class member to another
class member, so that I can later mutate the stored reference. I'd like
to use this for an undo function - basically, when you make a change to a
class member value, you call SaveMemberValue() to store the reference and
original value. Later, if necessary, you can Restore() the original value
without knowing at compile time which member was changed.

The illegal code below illustrates what I'm trying to accomplish. Another
approach that would work in my application is to have separate Restore()
functions for each class member, but I think that's ugly.

I'd appreciate any help with this. Thanks in advance.



using System;


class RestoreTest
{
private int a;
private int b;
private int c;
private int savedValue;
//ReferenceToInt is a bogus type
private ReferenceToInt savedValueReference;

public static void Main(string[] args)
{
RestoreTest r = new RestoreTest();
r.a=1;
r.b=2;
r.c=3;
Console.WriteLine("r's values are {0}, {1}, and {2}",
r.a,r.b,r.c);
//Output: r's values are 1, 2, and 3 r.Mutate(ref r.a,6);
Console.WriteLine("r's values are {0}, {1}, and {2}",
r.a,r.b,r.c);
//Output: r's values are 6, 2, and 3
r.Restore();
Console.WriteLine("r's values are {0}, {1}, and {2}",
r.a,r.b,r.c);
//Output: r's values are 1, 2, and 3
}
private void Mutate(ref int member, int val)
{
member=val;
SaveMemberValue((referenceToInt)member,val);
}
private void SaveMemberValue(ReferenceToInt referenceToMember,
int val)
{
savedValueReference=referenceToMember;
savedValue=val;
}
private void Restore()
{
savedValueReference=savedValue;
}
}
 
M

Marc Gravell

Good article; FYI have added a comment - so I won't bother repeating it
here.

Marc
 
S

Steve Goldman

Thanks for your answers, guys. They are very helpful. My situation is
straightforward enough that I'll probably go for the reflection approach.

Regards,

Steve
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top