Strong Typing a property name in .NET
Asked Answered
H

6

10

Say I have a class with one property

Public Class MyClass
   Public Property MyItem() as Object
      ....
   End Property
End Class

I have to pass the name of the property to a function call. (Please don't ask why it should be done this way, its a third party framework). For example

SomeFunc("MyItem")

But what I would like to do is, change the string into a strongly typed parameter. Meaning, if the property name is renamed or changed, it should be reflected here too.

So something of this type :

Dim objectForStrongTyping as New MyClass()
SomeFunc(objectForStrongTyping.MyItem().Name())

I am sure this won't work. Is there a way this strong typing can be done? (C# or VB.NET, any thing is cool)

Harrisonharrod answered 31/12, 2009 at 6:7 Comment(0)
A
21

Here is a solution using classes from System.Linq.Expressions.

static MemberInfo GetMemberInfo<TObject, TProperty>(
    Expression<Func<TObject, TProperty>> expression
) {
    var member = expression.Body as MemberExpression;
    if (member != null) {
        return member.Member;
    }

    throw new ArgumentException("expression");
}

Just throw this in a class somewhere (ExpressionHelper?).

Usage:

class SomeClass {
    public string SomeProperty { get; set; }
}

MemberInfo member = GetMemberInfo((SomeClass s) => s.SomeProperty);
Console.WriteLine(member.Name); // prints "SomeProperty" on the console
Afterguard answered 31/12, 2009 at 6:18 Comment(1)
Nice, can this be wrapped into an extension?Episternum
S
5

In C# 6.0 There is a new feature called nameof. Basically you can do this:

var name = nameof(MyClass.MyItem);

Looking at Telerik code converter from C# to VB it seems this is the VB equivalent:

Dim name = nameof([MyClass].MyItem)

So you can do the following:

SomeFunc(nameof(MyClass.MyItem));

Here is the reference to microsoft documentation: https://learn.microsoft.com/en-us/dotnet/articles/csharp/language-reference/keywords/nameof

Swanherd answered 20/5, 2017 at 14:8 Comment(0)
S
3

This solution works in both C# and VB.NET, but the VB.NET syntax for lambda functions is not as clean, which would probably make this solution less attractive in VB. My examples will be in C#.

You can achieve the effect you want using the lambda function and expression tree features of C# 3. Basically, you would write a wrapper function called SomeFuncHelper and call it like this:

MyClass objForStrongTyping = new MyClass();
SomeFuncHelper(() => objForStrongTyping.MyItem);

SomeFuncHelper is implemented as follows:

void SomeFuncHelper(Expression<Func<object>> expression)
{
    string propertyName = /* get name by examining expression */;
    SomeFunc(propertyName);
}

The lambda expression () => objForStrongTyping.MyItem gets translated into an Expression object which is passed to SomeFuncHelper. SomeFuncHelper examines the Expression, pulls out the property name, and calls SomeFunc. In my quick test, the following code works for retrieving the property name, assuming SomeFuncHelper is always called as shown above (i.e. () => someObject.SomeProperty):

propertyName = ((MemberExpression) ((UnaryExpression) expression.Body).Operand).Member.Name;

You'll probably want to read up on expression trees and work with the code to make it more robust, but that's the general idea.

Update: This is similar to Jason's solution, but allows the lambda expression inside the helper-function call to be a bit simpler (() => obj.Property instead of (SomeType obj) => obj.Property). Of course, this is only simpler if you already have an instance of the type sitting around.

Spectrophotometer answered 31/12, 2009 at 6:41 Comment(0)
P
1

If there is only one property you could do this - get the property info on the first property of the class:

//C# syntax
typeof(MyClass).GetProperties()[0].Name;

'VB syntax
GetType(MyClass).GetProperties()(0).Name

EDIT Turns out, where you can use expressions, you can also use projection for this kind of reflection (C# code).

public static class ObjectExtensions {
    public static string GetVariableName<T>(this T obj) {
        System.Reflection.PropertyInfo[] objGetTypeGetProperties = obj.GetType().GetProperties();

        if(objGetTypeGetProperties.Length == 1)
            return objGetTypeGetProperties[0].Name;
        else
            throw new ArgumentException("object must contain one property");
    }
}

class Program {
    static void Main(string[] args) {
        Console.WriteLine(Console.WriteLine(new { (new MyClass()).MyItem}.GetVariableName()););
    }
}

With this solution, the class can have any number of properties, you would be able to get any other their names.

Plate answered 31/12, 2009 at 6:18 Comment(3)
Ouch. Now instead of being worrying about Properties changing names, we have to worry about adding/reordering Properties?Shade
Yeah, it's pretty dirty, but as per the OP, the class only has one property. See edit for a better solution.Plate
With the EDIT, this is a very interesting answer. The projection method is nice. Is there a recommendation for which one makes more sense to use? In VB.Net the anonymous type syntax is more terse than the lambda syntax.Headfirst
A
0

You could always use a static class that contains string constants instead of passing in a string literal:

public static class ObjectForStrongTyping
{
    public const string MyItem = "MyItem";
    public const string MyOtherItem = "MyOtherItem";
    // ...
}

Your code would then become:

SomeFunc(ObjectForStrongTyping.MyItem);
Aruspex answered 31/12, 2009 at 6:13 Comment(1)
+1: while its not as "automatic" as the LINQ and typeof(...) solutions, its super simple and maintenance is minimal.Chophouse
N
0

The best solution I think is to generate static constants using T4 (e.g. T4MVC).

public static class StaticSampleClass
{
    public const string MyProperty = "MyProperty";
}

Believe me when you have lots of calls reflection and linq expression is taking down the performance of your application.

Bad thing is T4 is gone in net core. :(

Good thing in C#6.0 u can use nameof(SampleClass.MyProperty)

In the worst case u can use the following example:

using System.Linq.Expressions;

namespace ConsoleApp1
{
    public static class Helper
    {
        public static string GetPropertyName<T>(Expression<Func<T, object>> propertyExpression)
        {
            var member = propertyExpression.Body as MemberExpression;
            if (member != null)
                return member.Member.Name;
            else
                throw new ArgumentNullException("Property name not found.");
        }
        public static string GetPropertyName<T>(this T obj, Expression<Func<T, object>> propertyExpression)
        {
            return GetPropertyName(propertyExpression);
        }
    }

    public class SampleClass
    {
        public string MyProperty { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // Property name of type
            Console.WriteLine(Helper.GetPropertyName<SampleClass>(x => x.MyProperty));

            // Property name of instance
            var someObject = new SampleClass();
            Console.WriteLine(someObject.GetPropertyName(x => x.MyProperty));

            Console.ReadKey();
        }
    }
}

Performance results (1 million times call):

StaticSampleClass.MyProperty - 8 ms

nameof(SampleClass.MyProperty) - 8 ms

Helper.GetPropertyName<SampleClass>(x => x.MyProperty) - 2000 ms

Neille answered 20/5, 2017 at 12:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.