Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Extension methods on "members"

I have some extension methods which could be used like this:

MyType myObject; 
string displayName = myObject.GetDisplayName(x => x.Property);

The problem here is that it needs an instance, even if the extension method only needs the type MyType. So if there is no instance, it needs to be called like this:

string displayName = BlahBlahUtility.GetDisplayName((MyTpe x) => x.Property);

Which is not so nice anymore.

Is there a way to write better syntax for such cases?

What I actually want to do is this (pseudo language):

string displayName = MyType.Property.GetDisplayName()

Which of course does not work with C#.

But what about something like this:

string displayName = ((MyType x) => x.Property).GetDisplayName();

This is also not possible (after a lambda, a dot is not accepted).

Any ideas?


Edit:

My "favorite syntax" MyType.Property.GetDisplayName() seems to be misleading. I don't talk about static properties here. I know that this syntax won't be possible. I just tried to show in pseudo language, what information is necessary. This would be ideal, every additional stuff is just syntactical overhead. Any working syntax that is close to this would be great.

I don't want to write a certain extension method. I want an easy, readable and compile time safe syntax, using any language feature.

like image 979
Stefan Steinegger Avatar asked Jan 15 '10 11:01

Stefan Steinegger


2 Answers

Have a look at the Express and Reflect classes in the Lokad Shared Libraries. Think they may help out with what you are trying to do. Read more here:

  • Strongly Typed Reflection in Lokad Shared
  • How to Find Out Variable or Parameter Name in C#?
like image 133
Svish Avatar answered Oct 23 '22 07:10

Svish


From your comment: "I want an easy and compile time safe syntax to get information about members".

This is a very frequently requested feature and has been discussed in the C# team's meetings for about a decade, but has never been prioritised high enough to be included.

This blog post explains why:

http://blogs.msdn.com/ericlippert/archive/2009/05/21/in-foof-we-trust-a-dialogue.aspx

So for now, you're just going to be fighting against a missing feature. Maybe you could post more information about your broader problem and see if people can suggest different approaches.

Update

Without more info about your problem this is just guesswork. But if you have a property that represents a value but also carries additional "meta" information, you could always represent that as a new type and use an "injection" step to set everything up.

Here's a suggested abstract interface to such a "meta property":

public interface IMetaProperty<TValue>
{
    TValue Value { get; set; }

    string DisplayName { get; }

    event Action<TValue, TValue> ValueChanged;
}

The value of the property is just another sub-property, with its type defined by the user.

I've put in the display name, and also as a bonus you've got an event that fires when the value changes (so you get "observability" for free).

To have properties like this in a class, you'd declare it like this:

public class SomeClass
{
    public IMetaProperty<string> FirstName { get; private set; }
    public IMetaProperty<string> LastName { get; private set; }
    public IMetaProperty<int> Age { get; private set; }

    public SomeClass() { MetaProperty.Inject(this); }
}

Note how the setters on the properties are private. This stops anyone from accidentally setting the property itself instead of setting the Value sub-property.

So this means the class has to set up those properties so they aren't just null. It does this by calling a magic Inject method, which can work on any class:

public static class MetaProperty
{
    // Make it convenient for us to fill in the meta information
    private interface IMetaPropertyInit
    {
        string DisplayName { get; set; }
    }

    // Implementation of a meta-property
    private class MetaPropertyImpl<TValue> : IMetaProperty<TValue>, 
                                             IMetaPropertyInit
    {
        private TValue _value;

        public TValue Value
        {
            get { return _value; }
            set
            {
                var old = _value;
                _value = value;
                ValueChanged(old, _value);
            }
        }

        public string DisplayName { get; set; }

        public event Action<TValue, TValue> ValueChanged = delegate { };
    }

    public static void Inject(object target)
    {
        // for each meta property...
        foreach (var property in target.GetType().GetProperties()
            .Where(p => p.PropertyType.IsGenericType && 
                        p.PropertyType.GetGenericTypeDefinition() 
                            == typeof(IMetaProperty<>)))
        {
            // construct an implementation with the correct type
            var impl = (IMetaPropertyInit) 
                typeof (MetaPropertyImpl<>).MakeGenericType(
                    property.PropertyType.GetGenericArguments()
                ).GetConstructor(Type.EmptyTypes).Invoke(null);

            // initialize any meta info (could examine attributes...)
            impl.DisplayName = property.Name;

            // set the value
            property.SetValue(target, impl, null);
        }
    }
}

It just uses reflection to find all the IMetaProperty slots hiding in the object, and fills them in with an implementation.

So now a user of SomeClass could say:

var sc = new SomeClass
             {
                 FirstName = { Value = "Homer" },
                 LastName = { Value = "Simpson" },
                 Age = { Value = 38 },
             };

Console.WriteLine(sc.FirstName.DisplayName + " = " + sc.FirstName.Value);

sc.Age.ValueChanged += (from, to) => 
    Console.WriteLine("Age changed from " + from + " to " + to);

sc.Age.Value = 39;

// sc.Age = null; compiler would stop this

If you're already using an IOC container you may be able to achieve some of this without going directly to reflection.

like image 1
Daniel Earwicker Avatar answered Oct 23 '22 09:10

Daniel Earwicker