1

I've got a class defined like this:

public abstract class Uniform<T>
{
    public abstract string GlslType { get; }
    ...
}

And then a subclass defined like this:

public class UniformInt : Uniform<int>
{
    public override string GlslType
    {
        get { return "int"; }
    }
}

And then a method somewhere else that looks like this:

    public static string GetCode<T>()
    {
        var sb = new StringBuilder();
        var type = typeof(T);
        sb.AppendFormat("struct {0} {{\n", type.Name);
        var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
        foreach(var f in fields)
        {
            sb.AppendFormat("    {0} {1};\n", f.FieldType.GetProperty("GlslType").GetValue(???), f.Name);
        }
        ...
    }

I'm having trouble filling in the ???s. I believe GetValue expects an instance of the object, but I don't really care what instance it is because they all return the same value. And AFAIK there's no such thing as a public abstract static readonly value, so I have to use properties.

So what can I put in place of those ???s to get back "int" (assuming one the fields was a UniformInt).

As a side: How can I limit fields to only field types that inherit Uniform<>?

mpen
  • 272,448
  • 266
  • 850
  • 1,236

4 Answers4

2

You need an instance of UniformInt in order to get the value of a non-static property:

UniformInt someUniformInt = ...
f.FieldType.GetProperty("GlslType").GetValue(someUniformInt, null)

As a side: How can I limit fields to only field types that inherit Uniform?

bool isDerivesFromUniformOfInt = typeof(Uniform<int>)
    .IsAssignableFrom(f.FieldType);

or if you don't know the type of T in advance:

bool isDerivesFromUniformOfT = typeof(Uniform<>)
    .MakeGenericType(typeof(T))
    .IsAssignableFrom(f.FieldType);
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • There's no other way? I don't really want instantiate one using their existing constructors because it would have ill effects.... I'd have to write an `internal` constructor I think, but then I'd have to add said secret constructor to *every* subclass of `Uniform` just so that I can obtain the value of `GlslType`? – mpen Jan 10 '12 at 07:32
  • @Mark, think logically: you want to get the value of a non-static property. Without an instance of a type? This has strictly nothing to do with any reflection or whatever. Its simple and basic OOP rule. You can only get values of static properties without needing an instance of the containing class. – Darin Dimitrov Jan 10 '12 at 07:36
  • I meant if we change `GlslType` to something else that can be obtained more easily. That's not set in stone. – mpen Jan 10 '12 at 07:38
  • The aside bit... I guess I shouldn't have wrote `Uniform` because I didn't mean the same `T` used to call `GetCode`.. I meant the generic `Uniform<>`. `type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => typeof(Uniform<>).IsAssignableFrom(f.FieldType))` doesn't return anything... I'm thinking because `Uniform<>` is abstract... nothing is assignable from it? – mpen Jan 10 '12 at 07:50
  • @Mark, no, it's because `f.FieldType` is a closed generic type. So you need to specify the generic argument as explained in mu answer: `typeof(Uniform<>).MakeGenericType(SomeType).IsAssignableFrom(f.FieldType)`. It's normal that `typeof(Uniform<>).IsAssignableFrom(f.FieldType)` returns false. `f.FieldType` doesn't derive from `Uniform<>`. It derives from `Uniform` which is what it is assignable from. – Darin Dimitrov Jan 10 '12 at 07:54
  • Well then what do I fill in for `SomeType`? I don't know what `SomeType` is beforehand, and it isn't `T`. – mpen Jan 10 '12 at 08:02
  • @Mark, in this case you could use the BaseType: `typeof(Uniform<>).IsAssignableFrom(typeof(f.FieldType).BaseType.GetGenericTypeDefinition())`. Be careful though as if BaseType is not generic type, calling `GetGenericTypeDefinition` might throw. To test whether it is a generic type you could test the `IsGenericType` property first. – Darin Dimitrov Jan 10 '12 at 08:12
  • You've got an extra `typeof()` in there, but from what I gather, I should do this: `var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => f.FieldType.BaseType.IsGenericType && typeof(Uniform<>).IsAssignableFrom(f.FieldType.BaseType.GetGenericTypeDefinition()));`. *That* seems to work. Thanks! – mpen Jan 10 '12 at 08:27
  • @Mark, indeed. I made an error. No need of typeof around f.FieldType. – Darin Dimitrov Jan 10 '12 at 08:30
1

The problem is that since your property is not static the compiler doesn't know that they all return the same value. Since your UniformInt is not sealed, another user could inherit from it and override GlslType to return something else. Then UniformInt and all derived classes could be used for your GetCode<T>() method.

A static method would really be the best option. To make sure that you implement them on all classes (something you can't force because static methods can't be abstract) I would write a simple unit test that uses reflection to load all classes that inherit from Uniform<T> and check if they have the static property defined.

UPDATE

When thinking about how Attributes could help and after some experimenting I came up with the following. It definitely won't win a beauty contest but as a learning exercise it was helpful ;)

using System;
using System.Linq;

namespace StackOverflow
{
    internal class StackOverflowTest
    {
        private static void Main()
        {
            string sInt = UniformInt.GlslType;
            string sDouble = UniformDouble.GlslType;
        }
    }

    public abstract class Uniform<B, T> // Curiously recurring template pattern 
        where B : Uniform<B, T>
    {
        public static string GlslType
        {
            get
            {
                var attribute = typeof(B).GetCustomAttributes(typeof(GlslTypeAttribute), true);

                if (!attribute.Any())
                {
                    throw new InvalidOperationException(
                        "The GslType cannot be determined. Make sure the GslTypeAttribute is added to all derived classes.");
                }

                return ((GlslTypeAttribute)attribute[0]).GlslType;
            }
        }
    }

    [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
    internal sealed class GlslTypeAttribute : Attribute
    {
        public string GlslType { get; private set; }

        public GlslTypeAttribute(string glslType)
        {
            GlslType = glslType;
        }
    }

    [GlslType("int")]
    public class UniformInt : Uniform<UniformInt, int> // Curiously recurring template pattern 
    {
    }

    [GlslType("double")]
    public class UniformDouble : Uniform<UniformDouble, double> // Curiously recurring template pattern 
    {
    }
}
Wouter de Kort
  • 39,090
  • 12
  • 84
  • 103
  • Woah -- `public class UniformInt : Uniform` -- doesn't that create a circular reference? – mpen Jan 10 '12 at 08:21
  • 1
    @Mark Nope :) It's even a pattern: Curiously recurring template pattern (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) The code can be run as a console application without any problems – Wouter de Kort Jan 10 '12 at 08:24
1

The GlslType is not static, so you need an object reference before you can access it's value. The subject of static properties in abstract classes has been covered extensively already, ie:

Community
  • 1
  • 1
w5l
  • 5,341
  • 1
  • 25
  • 43
  • From these I've gathered 3 possible solutions: (1) use a static method as Wouter suggests, (2) use a static property in the base class to guarantee it's existence, then override with "new" in derived classes, (3) use a class attribute. – mpen Jan 10 '12 at 07:54
0

Solution 1

Add static methods to all derived classes that return the GlslType. Nothing needs to be added to the base class. Can use unit testing + reflection to check for missing implementation. Suggested by Wouter de Kort.

Solution 2

Change Uniform<T> to make GlslType static:

public abstract class Uniform<T>
{
    public static string GlslType { get { throw new NotImplementedException("Please override with \"new\" in derived class."); } }
    ...
}

Change UniformInt to "override" GlslType, keeping the static modifier:

public class UniformInt : Uniform<int>
{
    public new static string GlslType
    {
        get { return "int"; }
    }
}

Fill ??? with null, null:

sb.AppendFormat("    {0} {1};\n", f.FieldType.GetProperty("GlslType").GetValue(null,null), f.Name);

Solution 3

Use attributes instead. Something like:

[GlslType("int")]
public class UniformInt : Uniform<int>
{
}

Conclusion

All 3 of these solutions are pretty similar and seem to have the same drawbacks (can't enforce derived class to implement it). Throwing an exception via method 1 or 2 will help find errors quickly, or with 3 I can just skip over classes that don't have the attribute by modifying my fields condition.

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236