views:

122

answers:

3

I have some C# code that needs to work like this

D d = new D();
foreach(C item in list)
{
    c.value++;
    d.Add(c);   // c.value must be incremented at this point
}
if(!d.Process())
    foreach(C item in list)
        c.value--;

It increments a value on each item in a list and then tries to do some processing on them. If the processing fails, it needs to blackout the mutation or the items.

The question it: is there a better way to do this? The things I don't like about this is that there are to may ways I can see for it to go wrong. For instance, if just about anything throws it gets out of sync.

One idea (that almost seems worse than the problem) is:

D d = new D();
var done = new List<C>();
try
{
    foreach(C item in list)
    {
        c.value++;
        done.Add(c);
        d.Add(c);
    }
    if(d.Process()) done.Clear();
}
finaly
{
    foreach(C c in done)
        c.value--;
}
+1  A: 

It's memory intensive, but you can make a copy of the list before incrementing. If the process succeeds, you can return the new list. If it fails, return the original list.

Robert Harvey
I'm working with reference semantics so that doesn't work unless I do a deep copy and even then it might not work if there are other references to the items.
BCS
A: 

Why don't you increment only when the process success?

I think a better way is to make the process method in D increments the c.value inside.

bashmohandes
see edit/comment. The upshot it that this is shim code that needs to work in a preexisting environment that has those requirements.
BCS
+2  A: 

Without knowing the full details of your application, I don't think it's possible to give you a concrete solution to the problem. Basically, there is no method that will always work in 100% of the situations you need something like this.

But if you're in control of the class D and are aware of the circumstance in which D.Process() will fail, you might be able to do one of two things that will make this conceptually easier to manage.

One way is to test D's state before calling the Process method by implementing something like a CanProcess function that returns true if and only if Process would, but without actually processing anything.

Another way is to create a temporary D object that doesn't actually commit, but runs the Process method on its contents. Then, if Process succeeded, you call some kind of Promote or Commit method on it that finalizes the changes. Similar to if you had a Dataset object, you can create a clone of it, do your transactional stuff in the clone, then merge it back into the main dataset if and only if the processing succeeded.

Welbog
I'd love to do #1 but I the "Processability" situation can change out from under me. Same issue with #2; I'd love to do it but it won't work in my case. (D.Process hits a network service that shouldn't hold locks between calls)
BCS
Do you have control over this network service at all?
Welbog
The service itself? Yes. The back-end code the service hits? Kinda, but not really. I'd rather not muck with it as it is also called from other code that I don't really understand.
BCS
Well, I think in this situation your original setup is as good as it gets. The best you can do is chunk it up so that you don't have to roll back the whole list every time (if that even makes sense in your situation).
Welbog