2013-10-20 1 views
2

Есть ли разумный способ в C# для достижения следующей конструкции (в псевдокоде):С атрибутами экземпляра # класса механизм

void Method(MyClass<Attribute1, Attribute2, ...> foo) 
{ 
    // here I am guaranteed that foo has the specified attributes 
} 

Где Attribute* являются, например, значение перечисления, таким образом, что только экземпляры MyClass экземпляра с атрибуты, требуемые методом, могут быть переданы методу (и в противном случае не скомпилированы)?

Я пробовал смотреть на дженерики, так как я знаю, что шаблоны C++ могут сделать эту работу таким образом, чтобы она казалась логической отправной точкой, но я не мог заставить ее работать элегантно (я попытался использовать интерфейсы для ограничения типов параметра в этот способ, но он был очень громоздким и откровенно непригодным, так как у меня есть как минимум 4 атрибута).

Я хочу сделать это, чтобы избежать множества раздражающих проверок в начале каждого метода. Я занимаюсь разработкой графики DirectX 11, поэтому я немного ограничен API, который не делает его особенно простым для передачи объектов в этом «безопасном типе» образом (в DirectX каждый ресурс имеет большую структуру «Описание», которая содержит информацию о том, что может и не может сделать ресурс, есть и нет, и т. д., и является утомительным и подверженным ошибкам анализировать, поэтому я пытаюсь написать обертку вокруг него для удобства моих и моих пользователей).

Я также не могу иметь разные типы классов для каждого случая, потому что есть много комбинаций, поэтому это кажется самым удобным способом написания кода, подобного этому, и я надеюсь, что C# сделает это возможным.


Я уверен, что есть название для этого вида функции языка (если вы знаете, пожалуйста, дайте мне знать, я бы гугл, но это своего рода трудно найти, когда вы не знаете, соответствующие ключевые слова ...)

+1

Вы хотите разрешить передачу только определенных значений вашему методу или ваш вопрос о том, как ограничить общий метод принятием только определенных типов? – elgonzo

+0

@elgonzo Не так много «типов», но данный класс с набором атрибутов, например. для класса текстур у меня был бы метод, который принимает только, скажем, 4-канальную текстуру с 32 битами на канал (так что это должно быть два атрибута), и метод будет принимать только экземпляры Texture, созданные с помощью «4-х каналов», и "32 бит на канал". Таким образом, это отчасти между дженериками и полномасштабным метапрограммированием, но действительно неудобно реализовывать с использованием дженериков и механизмов ковариации типов. – Thomas

+0

Создайте тип, представляющий 4-канальные текстуры 32bpc, и ограничьте свой метод этим типом. (Наследуйте его от базового класса, который реализует обычные вещи для разных типов текстур, так что вы не получите код спагетти ...) – elgonzo

ответ

2

Параметры общего типа в .NET должны быть самими типами. Вы не можете создать общий тип/метод, специфичный только для определенного значения параметра Generic type.

Если вы не хотите или не можете создать тип, представляющий значения атрибутов, к которым вы хотите ограничить свой метод, вам необходимо будет выполнить проверки на работоспособность в вашем методе, чтобы убедиться, что соответствующие значения атрибутов используются в предоставленном " foo ".


Использование определенных типов в качестве представления определенных значений атрибутов может быть ответом на заданную вами проблему, но у него есть недостаток в том, что не поддерживаются операторы switch-case (см. Ниже). Пожалуйста, также прочитайте заключительную записку в конце моего ответа.

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

class Texture<TChannels, TBPC> 
    where TChannels : INumOfChannels,new() 
    where TBPC : IBitsPerChannel,new() 

INumOfChannels и IBitsPerChannel просто интерфейсы и может быть пустым.
Ограничение () предотвращает создание конкретного типа текстуры с использованием самих интерфейсов.

для различных каналов и различного СКАДА, вы будете создавать пустые типы, простирающиеся от соответствующих базовых интерфейсов, например:

class FourChannels : INumOfChannels {}; 
class ThreeChannels : INumOfChannels {}; 

class BitsPerChannel_24 : IBitsPerChannel {}; 
class BitsPerChannel_32 : IBitsPerChannel {}; 

Используя это, вы можете ограничить общий метод для определенных комбинаций атрибутов. В случае, если ваш метод должен иметь дело только с 4-канальным и 32bpc текстуром:

void MyMethod<TChannels, TBPC>(Texture<TChannels, TBPC> foo) 
    where TChannels : FourChannels 
    where TBPC : BitsPerChannel_32 


Теперь каждая хорошая вещь также имеет темную стороны. Как бы вы сделали что-то подобное (написанное как псевдокод)?

switch (NumOfChannelsAttribute) 
{ 
    case FourChannels: 
     // do something 
     break; 

    case ThreeChannels: 
     // do something else 
     break; 
} 

Вы не можете, по крайней мере, не в легкой и простой способ, потому что «четырехканального» и «ThreeChannel» типы, а не целые значения.

Конечно, вы все равно можете использовать , если конструкции. Для этой работы вам будет необходимо реализовать свойство в общий тип текстуры, которая обеспечивает используемые атрибуты:

class Texture<TChannels, TBPC> where TChannels : INumOfChannels,new() where TBPC : IBitsPerChannel,new() 
{ 
    public Type ChannelsAttribute 
    { 
     get { return typeof(TChannels); } 
    } 

    public Type BitsPerChannelAttribute 
    { 
     get { return typeof(TBPC); } 
    } 
} 

В качестве если конструкции, вы могли бы использовать это следующим образом:

var myTex = new Texture<FourChannels, BitsPerChannel_32>(); 

if (myTex.ChannelsAttribute == typeof(FourChannels)) 
{ 
    ... do something with 4 channels 
} 
else 
{ 
    ... though luck, only 4 channels are supported... 
} 


Заключительная записка и советы:
Хотя это может сработать для вашей проблемы, использование этих «трюков» обычно является признаком ошибочного дизайна. Я думаю, что это хорошо инвестированное время, если вы перейдете к вариантам дизайна, которые вы сделали в своем коде, поэтому вам не нужно полагаться на костыли, подобные этому.

1

У C# такой функции нет. Вы упомянули, что пытались использовать интерфейсы, но не указываете, как это сделать. Как я хотел бы предложить вам попробовать использовать их является использование генериков с множественными ограничениями, например

void Method(T foo) where T : IAttribute1, IAttribute2, IAttribute3, IAttribute4 
{ 
} 

Скажем, один такой класс атрибут, то ICpuMappable, то вы можете ограничить типы, которые могут быть использованы с Method1 с:

void Method1(T foo) where T : ICpuMappable 
{ 
} 

и вы можете узнать все foo, перешедшие на Method1, являются процессорами.

Скорее всего, у вас будет много интерфейсов, но многие будут рассматриваться как «флаги», их не должно быть слишком сложно поддерживать.

+0

Как бы этот подход имел дело с противоречивыми классами? При таком подходе все еще можно было бы создать экземпляр 'foo', например,' ICpuMappable' и 'IImmutable', будет ли конструктор обрабатывать это, чтобы запретить такие комбинации? – Thomas

+0

Что значит «самопротиворечивость»? Если IImmutable расширит один из интерфейсов IAttribute, то ограничение будет выполнено. Если IImutable не расширяет эти IAttribute интерфейсы, тогда компилятор отклонит ваш код. – elgonzo

+0

@elgonzo Хорошо, взяв пример в ответе на ваш комментарий к вопросу «IFourChannels» и «IThreeChannels». Они оба расширяют 'IAttribute', но они бессмысленны вместе ... поэтому никакой экземпляр' foo' не должен быть экземпляром с обоими. Как это сделать? – Thomas

Смежные вопросы