2

Couldn't seem to think of a good title, my apologies. Feel free to change it to something more appropriate; I'd appreciate it.

I'm in an unusual position here (or maybe not so unusual). I have a base type, from which will branch many types that extend its functionality but remain pretty consistent. Some of these will more or less be duplicates, but to present data in different ways. They can all be managed in much the same manner, so what I've decided to do is create a "merging" class that will take two or more of these objects and allow control over them all at once, but I've resorted to using reflection. If I were to redefine all the members of the base class and simply redirect all sets/gets/etc, it's just plain wrong, because if the base class to the types I'm "merging" were to ever change, so must the merging class.

But this leads to a performance cost I believe can be avoided. A performance hit not just from reflection but from the predictable boxing/unboxing during reflection.

Here's a pseudo-code example of what I'm doing:

class SomeBase
{
    public virtual bool SomeBool { get; set; }
}

class SomeDerived : SomeBase
{
    // ... extends SomeBase
}

class SomeMerger
{
    private SomeBase[] collection;

    public SomeMerger(SomeBase[] collection)
    {
        this.collection = collection;
    }

    public void SetProperty(string propertyName, object value)
    {
        for (int i = 0; i < this.collection.Length; i++)
        {
            PropertyInfo pi = collection[i].GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
            if (pi != null)
                pi.SetValue(collection[i], value, null);
        }
    }

    // .. etc
}

Now, what I'd like is to be able to do is access members as if they were a single entity inside the merger class (e.g. "SomeMergerObject.SomeBool = true" would set all the SomeBool properties in all the objects it merges to true, making the syntax more natural). However, the only way I can see of doing this would be to redefine all methods and properties of the base class and redirect calls to them (which I don't think would be considered right). Is there any cleaner/better way of achieving this?

Sorry if I did a poor job at explaining this. Yell if you're confused and I'll try to clarify. :)

Edit:

I guess I need to clarify a bit. I suppose I was misleading when I said "I have this base type etc" -- the implementation is as it stands, and isn't mine, all I'm attempting to do is make it easier to work with. Instead of setting basic properties for several objects that all share in areas like visibility state (as an example), I thought it would be a nice feature (albeit a trivial one, and more work than it's worth... but for curiosity's sake, why not explore the idea?) to make a collection of objects behave as one. This isn't even coming close to a problem, just an idea for improvement that I felt like flirting with.

I wasn't suggesting "a new language feature," and the tone of my question was is there a way to do this, a clean and right way. I guess I presented my inquiry badly, sorry about that.

gotopie
  • 2,594
  • 1
  • 20
  • 23
  • 3
    This sounds like you're creating the problem. Maybe you can post some more details on your situation -- there's certainly a better design for what you're trying to achieve. – hackerhasid Jan 15 '10 at 16:33

4 Answers4

4

That sounds like you're creating a maintenance nightmare for no good reason. Your problem isn't so bad that you have to invent what is basically a new language feature.

Among other things, if you've got a set of types that differ only in terms of how they present information, then you can use other mechanisms other than inheritance to break out the common code. You can pass a delegate to their "render" methods, for instance, or use the Strategy pattern.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
  • The reason I'd used reflection was to avoid that "maintenance nightmare." If you're referring to the ending bit (redefining all members), that's specifically why I'm avoiding it (and won't implement it). And I'm not really having a problem, per se, I'm just asking if what I'd like to do is possible in a way that wouldn't become a disaster later on, as you said. :P – gotopie Jan 15 '10 at 16:32
  • 1
    The maintenance nightmare I'm talking about is your use of a relatively sophisticated feature (Reflection) to solve a relatively simple problem. Whoever comes after will have to understand and maintain your Reflection code. – John Saunders Jan 15 '10 at 16:33
  • I totally agree. The problem seems to have nothing to do with managing the data, but in how it is 'rendered' in one way or another. – Michael Bray Jan 15 '10 at 16:37
1

If I interpret the question correctly, then using the code from here:

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
public interface ISomeInterface {
    void SetSomeBool(bool value);
}
class SomeBase : ISomeInterface {
    public virtual bool SomeBool { get; set; }
    void ISomeInterface.SetSomeBool(bool value) { SomeBool = value; }
}
class SomeDerived : SomeBase {
    // ... extends SomeBase
}
static class Program {
    static void Main() {
        var item1 = new SomeBase();
        var item2 = new SomeDerived();
        var items = new List<ISomeInterface> { item1, item2};
        ISomeInterface group = GroupGenerator.Create(items);
        group.SetSomeBool(true);
        Console.WriteLine(item1.SomeBool); // true
        Console.WriteLine(item2.SomeBool); // true
        group.SetSomeBool(false);
        Console.WriteLine(item1.SomeBool); // false
        Console.WriteLine(item2.SomeBool); // false
    }
}

Note that it would also work with properties, but the get will have to throw an exception (the set applies to all). For that reason, I prefer the explicit method approach (no get on the interface). It could also be a set-only property, but they are really rare:

public interface ISomeInterface
{
    bool SomeBool { set; }
}
class SomeBase : ISomeInterface
{
    public virtual bool SomeBool { get; set; }
}
class SomeDerived : SomeBase
{
    // ... extends SomeBase
}
static class Program
{
    static void Main()
    {
        var item1 = new SomeBase();
        var item2 = new SomeDerived();
        var items = new List<ISomeInterface> { item1, item2};
        ISomeInterface group = GroupGenerator.Create(items);
        group.SomeBool = true;
        Console.WriteLine(item1.SomeBool); // true
        Console.WriteLine(item2.SomeBool); // true
        group.SomeBool = false;
        Console.WriteLine(item1.SomeBool); // false
        Console.WriteLine(item2.SomeBool); // false
    }
}
Community
  • 1
  • 1
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I was writing up something similar (but less powerful), but then I realised that my idea (like this) still wouldn't address the desire to be able to pass the *name* of the to-be-set property *as a string*. Here you still need to explicitly list in `ISomeInterface` all the properties you might ever want to set. I think there isn't anyway of avoiding reflection / boiler-plate code if this is a hard requirement. – AakashM Jan 15 '10 at 17:04
  • My interpretation is that this approach (with string) is just an illustration of the way he has it working now, via reflection. – Marc Gravell Jan 15 '10 at 17:43
  • You interpreted correctly. Pretty cool stuff. Your code directly led me to my answer, and to ultimately realizing it'd be a bad design choice. I learned some good things though and got to fiddle with some experimental code, no regrets. Thanks. ;) – gotopie Jan 15 '10 at 23:04
0

Seems like you're taking a strange path here. You have to use reflection if you don't know at compile time what type you're dealing with.

However you could use the new dynamic keyword in .net 4.0 and get your class to implement IDynamicMetaObjectProvider to defer call site checks till runtime.

Jan Bannister
  • 4,859
  • 8
  • 38
  • 45
0

This code uses ConcurrentDictionary<TKey, TValue> from .NET 4, but you could write it using Dictionary<TKey, TValue> and either make it not thread-safe or use coarse locking (the lock statement) and sacrifice a bit of performance.

public static class DynamicObjects
{
    private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<string, Action<object, object>>> _setters
        = new ConcurrentDictionary<Type, ConcurrentDictionary<string, Action<object, object>>>();

    public static void SetProperty(object instance, string property, object value)
    {
        if (instance == null)
            throw new ArgumentNullException("instance");
        if (property == null)
            throw new ArgumentNullException("property");
        if (property.Length == 0)
            throw new ArgumentException("The property name cannot be empty.", "property");

        Type type = instance.GetType();
        var settersForType = _setters.GetOrAdd(type, CreateDictionaryForType);
        var setter = settersForType.GetOrAdd(property, (obj, newValue) => CreateSetterForProperty(type, property));
        setter(instance, value);
    }

    private static ConcurrentDictionary<string, Action<object, object>> CreateDictionaryForType(Type type)
    {
        return new ConcurrentDictionary<string, Action<object, object>>();
    }

    private static Action<object, object> CreateSetterForProperty(Type type, string property)
    {
        var propertyInfo = type.GetProperty(property);
        if (propertyInfo == null)
            return (o, v) => ThrowInvalidPropertyException(type, property);

        var setterMethod = propertyInfo.GetSetMethod();
        if (setterMethod == null)
            return (o, v) => ThrowReadOnlyPropertyException(type, property);

        ParameterExpression instance = Expression.Parameter(typeof(object), "instance");
        ParameterExpression value = Expression.Parameter(typeof(object), "value");
        Expression<Action<object, object>> expression =
            Expression.Lambda<Action<object, object>>(
            Expression.Call(instance, setterMethod, Expression.Convert(value, propertyInfo.PropertyType)),
            instance,
            value);

        return expression.Compile();
    }

    private static void ThrowInvalidPropertyException(Type type, string propertyName)
    {
        throw new InvalidOperationException("The type '" + type.FullName + "' does not have a publicly accessible property '" + propertyName + "'.");
    }

    private static void ThrowReadOnlyPropertyException(Type type, string propertyName)
    {
        throw new InvalidOperationException("The type '" + type.FullName + "' does not have a publicly visible setter for the '" + propertyName + "' property.");
    }
}
Sam Harwell
  • 97,721
  • 20
  • 209
  • 280