1

I have a process that I want to apply to multiple value-type properties of an arbitrary object, such that each property is modified in some way by the process. A method that applies the process to any given property passed to it would seem to be the way to go, but because the property is a value type it doesn't get changed unless I pass it by reference, but of course the C# compiler prevents properties being passed by reference.

How can I achieve the following without the compiler objecting or having to write messy multiple lines that just repeat the same conditional code for each property?

        static internal void AssignStringValueOrLeaveIfNull(string newValue, string sampleValue)
        {
            if (!string.IsNullOrEmpty(newValue))
                sampleValue = newValue;
        }

...
            AssignStringValueOrLeaveIfNull(value1, anObject.SampleText1);
            AssignStringValueOrLeaveIfNull(value2, anObject.SampleText2);
            AssignStringValueOrLeaveIfNull(value3, anObject.SampleText3);
            AssignStringValueOrLeaveIfNull(value4, anObject.SampleText4);
            AssignStringValueOrLeaveIfNull(value5, anObject.SampleText5);
...etc, 30 times.

where anObject.SampleTextn are all strings.

I can't be the first person to have wanted to do something similar!

I'm using VS2008 (C#3.5)

TIA

haughtonomous
  • 4,602
  • 11
  • 34
  • 52
  • Why don't you create a 'reference/template' object and then use reflection to populate the properties of your object from this 'reference object' appropriately? Obviously using reflection has performance considerations, but it may be justified depending on how often this is going to be called. – SpruceMoose Sep 15 '14 at 11:34
  • By the way: `string` is **not** a value-type... it is a reference-type; the issue is nothing to do with value-type vs reference-type. – Marc Gravell Sep 15 '14 at 11:39
  • Thanks for clarifying that. I had forgotten that nuance. It's a reference type that thinks it's a value type when passed as an argument (because it is immutable) – haughtonomous Sep 16 '14 at 12:50

2 Answers2

4

You cannot. That concept does not exist. You would have to assign the value to a temporary local variable, use ref on the variable, and then assign it back to the property:

var tmp = anObject.SampleText1;
AssignStringValueOrLeaveIfNull(value1, ref tmp);
anObject.SampleText1 = tmp;

Or use a return value, which is probably simpler...

anObject.SampleText1 = AssignStringValueOrLeaveIfNull(value1, anObject.SampleText1);

ref works with:

  • fields
  • local variables
  • array elements
  • parameters

It does not work with properties, since properties are actually method calls, and the result of a method call does not have a sensible location to ref it from. Note: at the IL level, you can have ref return values from methods, which would theoretically allow for something akin to this - but it is not exposed in C# at the moment (if ever), and it would not work with properties as they exist today.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Thanks,the second approach brings its own problems elsewhere in the application, because assiging a value to the object's property marks the object as 'dirty' even if the new value is the same as the existing value, which forces what in this case will be an unnecessary database insert, and I am not allowed to change that. I guess I'll just have to have 30 lines of ugly code. I'm perplexed as to why the compiler prevents passing a value-type property by reference. It seems illogical. – haughtonomous Sep 15 '14 at 11:34
  • @NeilHaughton it has nothing to do with it being a value type; it is because properties are **method calls**, and the result of a method call is a **copy** of the returned value. As such, you would be mutating a copy on the stack that only exists temporarily, and will never be accessible afterwards. Basically, your change would go nowhere, which is almost certainly an error. – Marc Gravell Sep 15 '14 at 11:36
  • @NeilHaughton For info, that is why I mentioned IL-level `ref` return values; if the method returned a *reference* to a value (presumably the backing field), **then** it would be possible and useful. But that is not what a property does. – Marc Gravell Sep 15 '14 at 11:37
  • Thanks,. The first time I read your response I didn't see the "ref works with..." section, hence my comments. Understood now. Can a property (ie a method) be passed as a delegate, I wonder? – haughtonomous Sep 15 '14 at 11:43
  • @NeilHaughton no, because it is actually **two different** methods; the `get` and `set` are largely unrelated. You can cheat using `Expression`, but that is quite expensive - more overhead that you would usually want. – Marc Gravell Sep 15 '14 at 11:45
1

You could write an ugly extension method that takes an expression representative of the property you want to set, and give it a chance to check whether your new values are null or empty (or different from the destination) before assigning the value.

public static void SetPropertyValue<T>(this T target, Expression<Func<T, string>> memberLamda, string value)
{
    // Check if "new value" is null or empty and bail if so
    if (string.IsNullOrEmpty(value))
        return;

    var memberSelectorExpression = memberLamda.Body as MemberExpression;
    if (memberSelectorExpression != null)
    {
        var property = memberSelectorExpression.Member as PropertyInfo;
        if (property != null)
        {
            // Get the existing value and compare against the new value 
            // Only set the property if it's different from the existing value
            if ((string)property.GetValue(target, null) != value)
            {
                property.SetValue(target, value, null);
            }
        }
    }
}

Source

And then you could use it like:

anObject.SetPropertyValue(a => a.SampleText1, value1);
anObject.SetPropertyValue(a => a.SampleText2, value2);

This should allow you to avoid having the object marked as "dirty", but is rather expensive (as Marc mentioned in a comment on his answer).

Community
  • 1
  • 1
Cᴏʀʏ
  • 105,112
  • 20
  • 162
  • 194
  • Thanks. Unfortunately I mus tonly make the assignment if the new value is not a null, so this owuld not achieve what I have to do. it looks like I am going to have to have 30 lines like this: if (!string.IsNullOrEmpty(newValue1) && sample.SampleText1 != newValue1) sample.SampleText1 = newValue1; – haughtonomous Sep 15 '14 at 11:45
  • @NeilHaughton: Read the code. I have appropriate checks in place to handle null/empty/different values. – Cᴏʀʏ Sep 15 '14 at 11:46
  • Sorry, it looks like I am reading comments before they are finished! When I read your comment it was nothing like the latest version - it just checked if the new value was a non-null non-empty string and assigned that. Nothing to do with Expressions. – haughtonomous Sep 15 '14 at 11:51
  • @Neil: I ninja-edited my answer when you mentioned the "dirty" flag. – Cᴏʀʏ Sep 15 '14 at 11:52
  • I don't think performance is going to be an issue for this application, so I'll take a look at this. – haughtonomous Sep 15 '14 at 12:03