Как получить значение абстрактного свойства const с помощью отражения?

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

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

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

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

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


person mpen    schedule 10.01.2012    source источник


Ответы (4)


Проблема в том, что, поскольку ваше свойство не статично, компилятор не знает, что все они возвращают одно и то же значение. Поскольку ваш 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 
    {
    }
}
person Wouter de Kort♦    schedule 10.01.2012
comment
Ого - public class UniformInt : Uniform<UniformInt, int> - разве это не создает круговую ссылку? - person mpen; 10.01.2012
comment
@Mark Нет :) Это даже шаблон: любопытно повторяющийся шаблон шаблона (en.wikipedia.org/wiki/Curiously_recurring_template_pattern) Код можно без проблем запускать как консольное приложение. - person Wouter de Kort♦; 10.01.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);
person Darin Dimitrov    schedule 10.01.2012
comment
Другого пути нет? Я действительно не хочу создавать один экземпляр, используя их существующие конструкторы, потому что это будет иметь плохие последствия ... Мне бы пришлось написать конструктор internal, я думаю, но тогда мне пришлось бы добавить указанный секретный конструктор в каждый подкласс Uniform<T>, чтобы я мог получить значение GlslType? - person mpen; 10.01.2012
comment
@Mark, подумайте логически: вы хотите получить значение нестатического свойства. Без экземпляра типа? Это не имеет никакого отношения ни к каким размышлениям или чему-то еще. Это простое и базовое правило ООП. Вы можете получать значения только статических свойств, не требуя экземпляра содержащего класса. - person Darin Dimitrov; 10.01.2012
comment
Я имел в виду, если мы изменим GlslType на что-то другое, что будет легче получить. Это не высечено в камне. - person mpen; 10.01.2012
comment
Немного в стороне ... Думаю, мне не следовало писать Uniform<T>, потому что я не имел в виду тот же T, который использовался для вызова GetCode<T> .. Я имел в виду общий Uniform<>. type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => typeof(Uniform<>).IsAssignableFrom(f.FieldType)) ничего не возвращает ... Я думаю, потому что Uniform<> абстрактно ... из него ничего нельзя назначить? - person mpen; 10.01.2012
comment
@Mark, нет, это потому, что f.FieldType - закрытый универсальный тип. Поэтому вам нужно указать общий аргумент, как описано в ответе mu: typeof(Uniform<>).MakeGenericType(SomeType).IsAssignableFrom(f.FieldType). Это нормально, что typeof(Uniform<>).IsAssignableFrom(f.FieldType) возвращает false. f.FieldType не является производным от Uniform<>. Он происходит от Uniform<SomeType>, из которого и назначается. - person Darin Dimitrov; 10.01.2012
comment
Так что же мне заполнить для SomeType? Я не знаю заранее, что такое SomeType, и это не T. - person mpen; 10.01.2012
comment
@Mark, в этом случае вы можете использовать BaseType: typeof(Uniform<>).IsAssignableFrom(typeof(f.FieldType).BaseType.GetGenericTypeDefinition()). Однако будьте осторожны: если BaseType не является универсальным типом, вызов GetGenericTypeDefinition может вызвать ошибку. Чтобы проверить, является ли это универсальным типом, вы можете сначала протестировать свойство IsGenericType. - person Darin Dimitrov; 10.01.2012
comment
У вас там есть лишний typeof(), но, насколько я понимаю, я должен сделать следующее: var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance).Where(f => f.FieldType.BaseType.IsGenericType && typeof(Uniform<>).IsAssignableFrom(f.FieldType.BaseType.GetGenericTypeDefinition()));. Это, похоже, работает. Спасибо! - person mpen; 10.01.2012
comment
@Mark, действительно. Я сделал ошибку. Нет необходимости в typeof вокруг f.FieldType. - person Darin Dimitrov; 10.01.2012

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

person Willem    schedule 10.01.2012
comment
Из них я собрал 3 возможных решения: (1) использовать статический метод, как предлагает Воутер, (2) использовать статическое свойство в базовом классе, чтобы гарантировать его существование, затем переопределить новым в производных классах, (3) использовать атрибут класса. - person mpen; 10.01.2012

Решение 1

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

Решение 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 на "override" GlslType, сохранив модификатор static:

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>
{
}

Вывод

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

person mpen    schedule 10.01.2012