Hi.
I'm writing a system that underlies programmer applications and that needs to detect their access to certain data. I can mostly do so with properties, like this:
public class NiceClass {
public int x { get; set; }
}
Then I go in and tweak the get
and set
accessors so that they handle the accesses appropriately. However this requires that the users (application programmers) define all of their data as properties.
If the users want to use pre-existing classes that have "normal" fields (as opposed to properties), I cannot detect those accesses. Example:
public class NotSoNiceClass {
public int y;
}
I cannot detect accesses to y
. However, I want to allow the use of pre-existing classes. As a compromise the users are responsible for notifying me whenever an access to that kind of data occurs. For example:
NotSoNiceClass notSoNice;
...
Write(notSoNice.y, 0); // (as opposed to notSoNice.y = 0;)
Something like that. Believe me, I've researched this very thoroughly and even directly analysing the bytecode to detect accesses isn't reliable due to possible indirections, etc. I really do need the users to notify me.
And now my question: could you recommend an "elegant" way to perform these notifications? (Yes, I know this whole situation isn't "elegant" to begin with; I'm trying not to make it worse ;) ). How would you do it?
This is a problem for me because actually the situation is like this: I have the following class:
public class SemiNiceClass {
public NotSoNiceClass notSoNice { get; set; }
public int z { get; set; }
}
If the user wants to do this:
SemiNiceClass semiNice;
...
semiNice.notSoNice.y = 0;
They must instead do something like this:
semiNice.Write("notSoNice").y = 0;
Where Write
will return a clone of notSoNice
, which is what I wanted the set
accessor to do anyway. However, using a string is pretty ugly: if later they refactor the field they'll have to go over their Write("notSoNice")
accesses and change the string.
How can we identify the field? I can only think of strings, ints and enums (i.e., ints again). But:
- We've already discussed the problem with strings.
- Ints are a pain. They're even worse because the user needs to remember which int corresponds to which field. Refactoring is equally difficult.
- Enums (such as
NOT_SO_NICE
andZ
, i.e., the fields ofSemiNiceClass
) ease refactoring, but they require the user to write an enum per class (SemiNiceClass
, etc), with a value per field of the class. It's annoying. I don't want them to hate me ;)
So why, I hear you ask, can we not do this (below)?
semiNice.Write(semiNice.notSoNice).y = 0;
Because I need to know what field is being accessed, and semiNice.notSoNice
doesn't identify a field. It's the value of the field, not the field itself.
Sigh. I know this is ugly. Believe me ;)
I'll greatly appreciate suggestions.
Thanks in advance!
(Also, I couldn't come up with good tags for this question. Please let me know if you have better ideas, and I'll edit them)
EDIT #1: Hightechrider's suggestion: Expressions.
I don't know if Write(x =>semiNice.y, 0)
was meant to be based on the classes I wrote for my question (SemiNiceClass
, etc) or if it's just an example, but if it's the former it doesn't match the structure: there's no y
field in SemiNiceClass
. Did you mean Write(x =>semiNice.notSoNice.y, 0)
?
I'm not sure how you mean for me to use this... I'll post the code I've written:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Test.MagicStrings {
public class MagicStringsTest {
public static void Main(string[] args) {
SemiNiceClass semiNice = new SemiNiceClass();
// The user wants to do: semiNice.notSoNice.y = 50;
semiNice.Write(x => semiNice.notSoNice.y, 50);
}
}
public class SemiNiceClass {
public NotSoNiceClass notSoNice { get; set; }
public int z { get; set; }
public SemiNiceClass() {
notSoNice = new NotSoNiceClass();
}
public void Write(Func<object, object> accessor, object value) {
// Here I'd like to know what field (y, in our example) the user wants to
// write to.
}
}
public class NotSoNiceClass {
public int y;
}
}
How do I get that info in Write
? I can't find any way to extract that information out of the Func<,>
. Also, why write semiNice.Write(x => semiNice.notSoNice.y, 50);
instead of semiNice.Write(() => semiNice.notSoNice.y, 50);
, since we're not using x
for anything?
Thanks.
EDIT #2: Hans Passant's suggestion: replacing fields with properties.
This is what I originally intended to do, but recompiling is not an option.
EDIT #3: Ben Hoffstein's suggestion: dynamic proxies; LinFu.
I've already looked into this long and hard, and for relatively complex reasons I cannot use it. It would be too long to explain, but rest assured: if I could use it, I would. It's much, much neater than my current solution.