4

Is there a way to pass class 'members' as first-class values?

public class Bike {
    public Color BikeColour { get; set; }
    public bool  IsGirlsBike { get; set; }
}

I would like to then refer to the field names, without any notion of an object.

I guess what I want is something like an enum:

public enum BikeFields {BikeColour, IsGirlsBike};

but without defining it explicitly.

Is there a way to do this in C#?

Edit: Apologies for being so vague; I want to be able to refer to class members as first class things (almost like a bound type).

Set<Bike:T> whichFieldsHaveBeenDrawn = new Set<Bike:T>();

Bike:T is undefined, I hope the illustration below makes it clear how this new type will work.

whichFieldsHaveBeenDrawn.Include(Bike.BikeColour);

var remainingFields = Set.Subtract(Bike.GetAllFields(), whichFieldsHaveBeenDrawn);

Bike b = new Bike();
foreach (var field in remainingFields) { Draw(field, b); }

I think I can do this with reflection, but I want them qualified at compile time...

wmercer
  • 1,102
  • 2
  • 11
  • 24
  • And how will you use them in your code? – Nikhil Chavan Aug 04 '15 at 06:20
  • 4
    What *problem* are you trying to solve, that this "enum" would be part of a solution? This is just a bit too abstract at the moment. You've tagged this with Reflection, so I assume you're aware of what's possible with reflection (e.g. `MemberInfo`) – Damien_The_Unbeliever Aug 04 '15 at 06:20
  • Your question makes no sense in its current form. More details are needed. – Sam Axe Aug 04 '15 at 06:20
  • Please see edited question - – wmercer Aug 04 '15 at 07:08
  • As mentioned above, you could use Reflection to get the names of the properties and then combined with EnumBuilder you could create enums dynamically (see http://stackoverflow.com/a/24804723/174929) – nieve Aug 04 '15 at 07:37

3 Answers3

2

You cannot have a statically typed Enum at the same time with a class because they are compiled in the same step. So you need two steps, first one to have the class then to generate the corresponding Enum. One way to achieve this in two steps is using t4 templates like this:

1. Create a class library (let's say is called ClassLibrary). This will contain your Bike class.

2. In an Console app (or whatever other type of project you may need) add a t4 text template like this:

<#@ template debug="true" hostspecific="false" language="C#" #>
<#@ assembly name="$(TargetDir)\ClassLibrary.dll" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="ClassLibrary" #>
<#@ output extension=".generated.cs" #>

namespace SO_31801914
{

<#
    var classes = new Type[] { typeof(ClassLibrary.Bike) };

    foreach (var cls in classes)
    {
#>
    public enum <#=cls.Name#>Enum
    {
<#      var props = cls.GetProperties();        
        for (int i = 0 ; i < props.Length; i++)
        {
            var prop = props[i];
            bool skipComma = false;
            if (i == props.Length - 1)
            {
                skipComma = true;
            }
#>
            <#=prop.Name#><#=skipComma ? string.Empty : ","#>
<#
        }
#>
    }
<#
    }
#>
}

The result will be:

namespace SO_31801914
{

    public enum BikeEnum
    {
        BikeColour,
        IsGirlsBike
    }
}

Build ClassLibrary then right-click on template and click on "Run custom tool". In the TemplateName.generated.cs you will have the above result.

tomab
  • 2,061
  • 5
  • 27
  • 38
  • Thanks @tomab, this is the closest solution to what I had in mind - I wish the compiler was a bit more open to the user through the programming language. – wmercer Aug 05 '15 at 03:01
1

You can convert the properties and their values to a dictionary

var bike = new Bike() { BikeColour = Color.Red, IsGirlsBike = true };
var props = bike.GetType().GetProperties()
           .ToDictionary(p => p.Name, p => p.GetValue(bike, null));

EDIT

If I understand you correctly, you want to write a code something like this

var props = GetAllProperties<Bike>()
            .Except(new[] { GetProperty<Bike>(x => x.BikeColour) });
Draw(bike, props);

public IEnumerable<PropertyInfo> GetAllProperties<T>()
{
    return typeof(T).GetProperties();
}

public PropertyInfo GetProperty<T>(Expression<Func<T,object>> expr)
{
    var uExpr = expr.Body as UnaryExpression;
    var memberExpr = uExpr.Operand as MemberExpression;
    return memberExpr.Member as PropertyInfo;
}

public Dictionary<string,object> GetValues<T>(T obj, IEnumerable<PropertyInfo> props)
{
    return props.ToDictionary(p => p.Name, p => p.GetValue(obj, null));
}

void Draw(Bike b, IEnumerable<PropertyInfo> properties)
{
    var values = GetValues(b, properties);
}
EZI
  • 15,209
  • 2
  • 27
  • 33
  • And then what? That's not really an enum, and it doesn't do the same things that an enum does. – Robert Harvey Aug 04 '15 at 06:22
  • OP says, `I guess what I want is something like an enum`, So he is not sure. I showed the dynamic way. What is wrong with it. – EZI Aug 04 '15 at 06:22
  • Yes @EZI - Something like this... but compile time checked. Please see edited question. – wmercer Aug 04 '15 at 06:52
  • Thanks @EZI - GetProperty is very interesting; all the answers proposed here except nameof() are runtime - i.e. they cannot be checked without running the program. I hope this makes sense, tomab's answer below is the closest to what I had in mind - this information is kept in the compiler somewhere - but I suspect it get's thrown out. – wmercer Aug 05 '15 at 03:00
1

Just throwing this out there. But if you want to directly refer to members by name... why not use nameof?

class Foo
{
    public int A { get; set; }
    public int B { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var rendered = new List<string>();

        if (!rendered.Contains(nameof(Foo.A)))
        {
            //Do something
            rendered.Add(nameof(Foo.A));
        }
    }
}

if you really want an enum:

public enum FooFields
{
    A,
    B
}

var enumA = Enum.Parse(typeof (FooFields), nameof(Foo.A));
Meirion Hughes
  • 24,994
  • 12
  • 71
  • 122