4

I have a piece of code that is like this:

void someAlgorithm(SomeType someVar)
{
  someVar.someMember = createSomeValue();
  lock(something)
  {
    SomeType someOtherVar = something.Find(someVar.unrelatedMember);
    if(someOtherVar != null)
      someOtherVar.someMember = someVar.someMember;
  }
}

(I needed to adapt it a bit for posting, so please bear with me if I messed it up in doing so.)

Now I need this exact piece of code for another member of someVar (which has a related, but different type) and another creation function. I know I can just take this code, copy it, replace a few identifiers, and be done. But I feel dirty doing so. I feel there should be a way to generalize this little algorithm.

I know I can always pass the creation function as a delegate, but I don't know how to generalize the member access and then there's the issue of those members (and creation functions) having different types.

In C++, I would use member pointers combined with templates for doing this. Member pointers aren't exactly a piece of cake to use, but once I had looked up their weird syntax, I'd be done within a few minutes. How to do that in C#?

Edit: Since this doesn't seem clear enough, here's what that other instance of the same algorithm looks like:

void someOtherAlgorithm(SomeOtherType someVar) // 1 change here
{
  someVar.someOtherMember = createSomeOtherValue(); // 2 changes here
  lock(something)
  {
    SomeOtherType someOtherVar = something.Find(someVar.unrelatedMember);
    if(someOtherVar != null)
      someOtherVar.someOtherMember = someVar.someOtherMember; // 2 changes here
  }
}

I hope this clarifies this.

sbi
  • 219,715
  • 46
  • 258
  • 445

3 Answers3

3

I think the best option is to use a Func delegate and pass a simple selector function as an argument to the algorithm. You can make the method generic, so that the selector can return members of any type:

void someAlgorithm<T>(SomeType someVar, SomeType someOtherVar, 
                      Func<SomeType, T> selector) { 
  someVar.someMember = createSomeValue(); 
  lock(something) { 
    var someOtherVar = something.Find(selector(someVar));  // Use 'selector'
    if(someOtherVar != null) 
      someOtherVar.someMember = someVar.someMember; 
  } 
} 

Then you can write something like:

someAlgorithm(some1, some2, a => a.SomeOtherMember);

Without more details about your actual code, it is a bit difficult to write the answer precisely (e.g. you may need some constraints for the generic type parameter - e.g. IComparable if you're comparing the values), but this is generally the best approach to solving the problem.

If you need another parameterization in the code that sets/gets values of someMember then you would just add other functions. For example Action<SomeType, T> (which sets value) and one more "selector" function for the other member. In the call, this would be (s, val) => s.SomeMember = val. If someMember has a different type, then you'd probably need to add one more type parameter.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
  • 1
    I suggest you add another example how to make the assignment to someMember in a generic manner, too. – Doc Brown Oct 07 '10 at 16:13
  • Please read my question again: instead of `someMember` I need `someOtherMember`. Yours doesn't help with this. The algorithm is almost exactly as shown in my question. – sbi Oct 07 '10 at 16:38
3

You've confused me a bit, there. Your someAlgorithm takes a parameter of type SomeType called someOtherVar and declares a local variable with the same name (so it can't compile as is). From your two definitions it appears that someVar and someOtherVar are the same type (SomeType), but your local variable is only declared with var, so it isn't entirely clear whether they are or not.

In your comment to SLaks you imply that someVar and someOtherVar are of different types (even though in the part of your question that you quoted you were talking about different members of someVar while SLaks was asking about your two variables someVar and someOtherVar). So I'm going to assume they're different types and that someOtherVar is only a local variable, not a parameter.

Based on these assumptions:

void someAlgorithm<TMember>(
    SomeType someVar,
    Func<TMember> create,                   // replaces "createSomeValue"
    Func<SomeType, TMember> getter,         // replaces get for "someMember"
    Action<SomeType, TMember> setter,       // replaces set for "someMember"
    Action<SomeOtherType, TMember> setter2) // replaces set for "someMember"
                                            // on "someOtherVar" (not necessary
                                            // if "someOtherVar" is actually
                                            // the same type as "someVar")
{
  setter(somevar, create());

  lock(something)
  {
    SomeOtherType someOtherVar = something.Find(someVar.unrelatedMember);

    if(someOtherVar != null)
      setter2(someOtherVar, getter(someVar));
  }
}

For your first algorithm, this would be called like:

someAlgorithm(
    someVar,
    createSomeValue,
    x => x.someMember,
    (x, y) => { x.someMember = y; },
    (x, y) => { x.someMemberOfOtherType = y; }
);

For your second:

someAlgorithm(
    someVar,
    createSomeOtherValue,
    x => x.someOtherMember,
    (x, y) => { x.someOtherMember = y; },
    (x, y) => { x.someOtherMemberOfOtherType = y; }
);
Dan Tao
  • 125,917
  • 54
  • 300
  • 447
  • Sorry for the confusion. I suspected I messed up when I tried to extract that algorithm, but, of course, I didn't see it... Anyway, thanks for your effort in (rightly) deducing what I needed. It seems you have found the way to do what I wanted (`+1` from me for that), although it is as I feared: it's almost easier to maintain the copied code than that. `:(` Well, as of now the algorithm has considerably grown, and there's two more with the very same problem, so it seems this is worth the effort anyway. – sbi Oct 07 '10 at 17:59
  • @sbi: On the maintainability of this code: I agree, all of these delegates can quickly become very cumbersome. For this reason I would recommend possibly defining some *interface* that can deal with the different objects in this algorithm the way you want. This way instead of accepting a `SomeType` and 4 (!) delegates, your algorithm can accept a `SomeType` and an instance of an interface that provides 4 analogous methods. – Dan Tao Oct 07 '10 at 18:31
1

You can pass lambda expressions that create and set the values.

For example:

void someAlgorithm<TObject, TProperty>(TObject someVar, Func<TProperty> creator, Action<TObject, TProperty> setter, Action<SomeType, TProperty> relatedSetter)
{
  var value = creator();
  setter(someVar, value);
  lock(something)
  {
    var someOtherVar = something.Find(someVar.SomeOtherMember);
    if(someOtherVar != null)
      relatedSetter(someOtherVar, value);
  }
}


someAlgotihm(something, createSomeValue, (x, v) => x.someProperty = v, (x, v) => x.someProperty = v);
SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Please read my question again. I have added the second instance of the algorithm. I would also need to change the line `someOtherVar.someMember = someVar.someMember;` Passing the lambdas to do that makes that almost unreadable. Also, does this account for the different data members being of different types? – sbi Oct 07 '10 at 16:40
  • You can pass another lambda for `someOtherVar` (or reuse the first lambda is the type and property is the same). The generics allow for different types. – SLaks Oct 07 '10 at 16:42
  • It seems to me the `someVar.SomeOtherMember` part won't really work when `someVar` can be any arbitrary `TObject` (unless you add a generic constraint forcing `TObject` to derive from some base class). – Dan Tao Oct 07 '10 at 17:11
  • @Dan: Correct. A constraint or additional lamdba will be needed. – SLaks Oct 07 '10 at 17:24