0

Consider an interface like this:

new Provider().For(myClass).ExcludeProperties("Height", "Width");

public IEditableStateProvider For(object target) {...}
public IEditableStateProvider ExcludePropertyNames(params string[] propertyNames) {...}

I want to replace the params string[] propertyNames arg with params Expression<Func<object>>[] propertyNames so that I would instead have the following.

new Provider().For(myClass).ExcludeProperties(()=>Height, ()=>Width);

I have seen code similar to this so I think it should work, but I am not getting it yet. How can I get this to work?

EDIT - doing this without Generics

Here is some code from an open source project I was looking at where type inference is working without any generics. I was looking to do the same but I don't see where the type inference is coming from (I do see it working though!)

// USAGE (here this is being called from code-behind of a WPF window

private void TrackSelectedTab() {
    Services.Tracker.Configure(tabControl)
        .AddProperties(() => tabControl.SelectedIndex);
    Services.Tracker.ApplyState(tabControl);
}

private void TrackMainWindow() {
    Services.Tracker.Configure(this)
        .AddProperties(
            () => Height,
            () => Width,
            () => Left,
            () => Top,
            () => WindowState)
        .SetKey("MainWindow")
        .SetMode(PersistModes.Automatic);

    Services.Tracker.ApplyState(this);
}

// Collab classes

public class SettingsTracker
{   
    public TrackingConfiguration Configure(object target) {
        ...
        return config;
    }
}

public class TrackingConfiguration
{
    public TrackingConfiguration AddProperties(params Expression<Func<object>>[] properties) {
        ...
        return this;
    }
}

static class Services
{
    public static readonly SettingsTracker Tracker = new SettingsTracker(ObjectStore);
}
Berryl
  • 12,471
  • 22
  • 98
  • 182

1 Answers1

3

You should create a generic Provider class in addittion to a non-generic one, so you can take advantage of type inference and type safety:

Interface:

interface IEditableStateProvider<T>
{
     IEditableStateProvider<T> For(T target);
     IEditableStateProvider<T> ExcludePropertyNames(params Expression<Func<T, object>>[] excludedProperties);
} 

Dummy implementation:

class Provider<T> : IEditableStateProvider<T>
{
    public IEditableStateProvider<T> For(T target) 
    { 
        // dummy
        return this;
    }
    public IEditableStateProvider<T> ExcludePropertyNames(params Expression<Func<T, object>>[] excludedProperties) 
    {
        // dummy
        return this;
    }
}
class Provider
{
    // generic factory method to make use of type inference
    public static IEditableStateProvider<T> For<T>(T obj)
    {
        return new Provider<T>().For(obj);
    }
}

Usage:

var myClass = new List<object>(); // or whatever
Provider.For(myClass).ExcludePropertyNames(x => x.Count);

The type T is now infered when you call .For(myClass), and you can now access the properties of type T in a type safe manner via a lambda when calling ExcludePropertyNames.


If you really want a non-generic version:

interface IEditableStateProvider
{
     IEditableStateProvider For(object target);
     IEditableStateProvider ExcludePropertyNames(params Expression<Func<object>>[] excludedProperties);
} 
class Provider : IEditableStateProvider
{
    public IEditableStateProvider For(object target) 
    { 
        // dummy
        return this;
    }
    public IEditableStateProvider ExcludePropertyNames(params Expression<Func<object>>[] excludedProperties) 
    {
        // dummy
        return this;
    }
}

var myClass = new List<object>();
new Provider().For(myClass).ExcludePropertyNames(() => myClass.Count);

but please note my comment below.

sloth
  • 99,095
  • 21
  • 171
  • 219
  • I like your solution but I was thinking of a non-generic solution along the lines of the code I added in my original post EDIT. Do you see where the type inference is coming from there? Cheers – Berryl Feb 08 '13 at 14:34
  • 1
    The code you posted in your edit does not use type inference at all. While it is non-generic, it is also not type safe. Nothing prevents you from using it like e.g. `.Configure(tabControl).AddProperties(() => 12345);` (where the `int` 12345 is clearly not a property of `tabcontrol`). It works with `Height`, `Width` etc. because these are properties which are accessible because the `.AddProperties` is used a class that has these properties (I guess it's a `Form`). I strongly recommend using the generic approach, since it ensures you can only use properties of the type/object you pass to `For`. – sloth Feb 08 '13 at 14:49
  • @Berryl Nonetheless, I added a non-generic version. – sloth Feb 08 '13 at 14:58
  • I hadn't realized that non-generic code wasn't type safe (honestly I'm still not sure how it gets intellisense to pick up properties at all!). Thanks for the assist, cheers – Berryl Feb 08 '13 at 21:10