Как получить значение абстрактного свойства "const", используя отражение? - PullRequest
1 голос
/ 10 января 2012

У меня есть класс, определенный следующим образом:

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

И затем подкласс, определенный следующим образом:

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

А затем метод где-то еще, который выглядит так:

    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);
        }
        ...
    }

У меня проблемы с заполнением ??? с.Я считаю, что GetValue ожидает экземпляр объекта, но мне все равно, какой это экземпляр, потому что все они возвращают одно и то же значение.И AFAIK нет такой вещи как public abstract static readonly значение, поэтому я должен использовать свойства.

Так что я могу поставить вместо этих ??? s, чтобы получить обратно "int" (предполагая одно из полейбыл UniformInt).

В качестве стороны: Как я могу ограничить fields только теми типами полей, которые наследуют Uniform<>?

Ответы [ 4 ]

2 голосов
/ 10 января 2012

Вам нужен экземпляр UniformInt, чтобы получить значение нестатического свойства:

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

В качестве стороны: Как я могу ограничить поля только теми типами полей, которые наследуют Uniform?

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

или, если вы заранее не знаете тип T:

bool isDerivesFromUniformOfT = typeof(Uniform<>)
    .MakeGenericType(typeof(T))
    .IsAssignableFrom(f.FieldType);
1 голос
/ 10 января 2012

GlslType не является статичным, поэтому вам нужна ссылка на объект, прежде чем вы сможете получить доступ к его значению. Тема статических свойств в абстрактных классах уже широко освещена, а именно:

1 голос
/ 10 января 2012

Проблема в том, что поскольку ваше свойство не является статическим, компилятор не знает, что все они возвращают одно и то же значение.Поскольку ваш UniformInt не запечатан, другой пользователь может унаследовать его и переопределить GlslType, чтобы вернуть что-то еще.Тогда UniformInt и все производные классы могут быть использованы для вашего GetCode<T>() метода.

Статический метод действительно будет лучшим вариантом.Чтобы убедиться, что вы реализуете их во всех классах (то, что вы не можете форсировать, потому что статические методы не могут быть абстрактными), я бы написал простой модульный тест, который использует отражение, чтобы загрузить все классы, наследуемые от Uniform<T>, и проверить, если ониопределите статическое свойство.

ОБНОВЛЕНИЕ

Размышляя о том, как атрибуты могут помочь, и после некоторых экспериментов я придумал следующее.Это определенно не выиграет конкурс красоты, но как учебное упражнение это было полезно;)

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 
    {
    }
}
0 голосов
/ 10 января 2012

Решение 1

Добавить статические методы ко всем производным классам, которые возвращают GlslType.Ничего не нужно для добавления в базовый класс.Можно использовать модульное тестирование + отражение, чтобы проверить отсутствие реализации.Предложено Wouter de Kort .

Решение 2

Изменить Uniform<T>, чтобы сделать GlslType статическим:

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

Изменить UniformInt"переопределить" GlslType, сохраняя статический модификатор:

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

Заполнить ??? null, null:

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

Решение 3

Использование атрибутов вместо .Что-то вроде:

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

Заключение

Все 3 из этих решений довольно похожи и, похоже, имеют те же недостатки (не могут принудительно реализовать производный класс).Создание исключения с помощью метода 1 или 2 поможет быстро найти ошибки, а с помощью 3 я могу просто пропустить классы, которые не имеют атрибута, изменив условие fields.

...