2017-02-16 6 views
1

CUrrently в процессе окончательного изучения C#. Но после использования C++ и python это одна вещь, которая продолжает ударять меня во время написания C#.Общий шаблон «идентификатор» в C#

C# не имеет аналогичной вещи для typedef в C++. (Или, по крайней мере, htat истинно в соответствии с различными сообщениями здесь другие результаты поиска.

Теперь первое использование, чтобы «написать псевдоним», я могу понять (хотя из опыта не согласен - но это то, что я могу научиться принимать).

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

пример, скажем, у меня есть словарь, который связывает «ценность» для некоторых «идентификаторов»:

System.Collections.Generics.Dictionary<string, double> 

Был бы логичное начало. Однако скажите в будущем, имея более четкую картину всего приложения, я хочу изменить его. Я замечаю, что для вычислений мне действительно нужны десятичные значения вместо плавающей запятой - (или, может быть, даже бигновы вместо плавающих точек). Мне пришлось бы переменить весь мой код, изменив это. Подобно идентификатору: строки «легкие», но, возможно, в будущем я действительно не хочу использовать такие раздутые структуры. Скорее я использую что-то, что «может быть преобразовано из строк и является достаточно уникальным» в моем классе Или, в другом будущем, я, возможно, не захочу использовать общий словарь: вместо этого я реализую пользовательский для этого класса.

Все это потребует от меня изменения кода в разных местах. Потенциальная ошибка, и, таким образом, сопровождающий будет выбирать не изменять ее из-за проблем обслуживания.

На других языках я узнал, что это было разрешено либо «не заботьтесь» (python), либо разрешив typedef. И всегда использовать этот typedef, также в других классах.

Что такое идиоматический способ сделать это на C#? Общепризнано ли использование длинных «списков» общих переменных в определении вашего класса?

MyClass<A_KeyType, A_ValueType, B_KeyType, B_ValueType, ContainerType> 

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


Как очень упрощенно (глупый) пример

public class MyClass { 
    public MyClass() { } 
    private Systems.Collections.Generics.Dictionary<string, double> Points = new Systems.Collections.Generics.Dictionary<string, double>() 
    Public void AddPerson(string studentID, double val) { 
     Points.Add(studentID, val) 
    } 

} 

добытчики, возможно, смена и т.д. бы все должны явно ссылаться на Systems.Collections.Generics.Dictionary<string, double>, хотя, возможно, в будущем studentID будет простое числовым значением или что-то другое. Также код «использование» этого, который «получает» идентификатор студента, должен понимать, что это строка.

В C++ я бы параметризуем тип студента «под» мой класс, как:

public class MyClass { 
    typedef string StudentIDType 
    ... 

Тогда я хотел бы использовать этот явный тип MyClass.StudentIDType во всех ситуациях.

+0

Не уверен, что я понимаю это право, но для меня вся концепция написания кода выглядит странно. Когда вы пишете код, вы должны знать, для чего он используется, какие данные задействованы и какие типы вам нужны. Однако есть что-то в C#, которое приблизилось бы к тому, что вы, похоже, ищете. 'var your_var = 1',' var' _type_ (это не тип), просто говоря, выбирает правильный тип для вас. Таким образом, вы можете пропустить все варианты выбора для ваших переменных. – r41n

+0

У Python нет дженериков, а C++ использует * частную спецификацию шаблона *, а не 'typedef' или aliasing, чтобы делать то, что вы описываете. –

+1

* «В будущем, когда я буду иметь более четкое изображение всего приложения, я хочу изменить его» * ​​- звучит как перестройка для меня. В C# есть дженерики, они будут хорошо работать для * any * 'T'. Вы можете использовать интерфейсы, отражение, «Конвертировать», «Картпер», «Whatelseiforgot» для достижения независимости по типу ... – Sinatr

ответ

2

C# не имеет подобную вещь TYPEDEF в C++.

Typedef in C определяет новый тип. C# имеет псевдонимы типа:

using Frob = System.Collections.Dictionary<System.String, System.String>; 
... 
Frob f = new Frob(); 

Но это на файл. Именованный псевдоним не является членом какого-либо пространства имен или типа.

и C#, конечно, позволяет определять новые типы оберточными старых:

struct MyOpaqueIdentifier 
{ 
    private int id; 
    public MyOpaqueIdentifier(int id) { this.id = id; } 
    ... now define equality, conversions, etc ... 
} 

Однако сказать, в будущем, при наличии более четкую картину всего приложения, я хотел бы изменить его

Это преждевременная ошибка общности, также известная как YAGNI: вам это не понадобится. Существуют бесконечные способы разработки программ, которые будут более общими, большинство из которых вам никогда не понадобятся. Правильный способ сделать это - это задуматься над тем, какие общие принципы вам понадобятся впереди, и спроектировать их с самого начала.

Кроме того, помните, что Visual Studio имеет мощные инструменты рефакторинга.

Что такое идиоматический способ сделать это на C#? Общепризнано ли использование длинных «списков» общих переменных в определении вашего класса?

C# позволяет выразить общность несколько способов:

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

Звучит так же, как и вы • рассмотрение злоупотреблений дженериками; обычно используется один из других подходов.

0

Попробуйте что-то вроде этого:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var obj = new Class1<int, string>(); 
     obj.Dictionary.Add(1, "hello"); 
    } 
} 

class Class1<Tkey, Tvalue> 
{ 
    public Dictionary<Tkey, Tvalue> Dictionary { get; set; } 
} 
+0

Кажется, что OP знает, как использовать дженерики, они ищут что-то еще. –

0

Если вы хотите Python путь, а затем использовать Dictionary<string, object>

Вы жертвовать производительностью и может работать в много проблем во время выполнения за счет сведения к минимуму изменения кода.

Я действительно не вижу в этом никакого значения.Составитель по-прежнему должен идти во все места, где вы использовали float, и заменять все переменные и входы. Вам не хватает смысла использовать строго типизированный скомпилированный язык.

Лучше всего, чтобы создать общий класс, который окружит функциональность

class MyClass<T> 
{ 
    Dictionary<string, T> innerDict; 
} 
0

Вы, кажется, имеют фундаментальное непонимание дженериков в C#. Они не предназначены для облегчения рефакторинга, так как ваш C++ с typedef, похоже, подготовлен для будущих сопровождающих, чтобы переключить тип. Такое использование кажется неправильным, и, хотя я не кодирую на C++, я предполагаю, что это меньше используется как «общий» и более «анонимный класс». То есть вы на самом деле определяете псевдослот типа StudentIDType, единственным свойством которого является строковое значение, доступ к которому вы можете получить напрямую через «псевдоним». В C# есть такие понятия, как анонимные классы (см. Закрытие), но они определяются как вход для некоторой функции. Скорее, метод C# для обработки вышеуказанной ситуации заключается в правильном подтверждении псевдокласса как явно объявленного класса. Такая реализация будет выглядеть следующим образом:

public class MyClass { 
    // DO NOT EVER DO THIS 
    // classes should not contain public classes this is merely the smallest possible example 
    public class StudentPoints { 
     public string StudentId { get; set; } 
     public double PointsValue { get; set; } 
    } 

    private IEnumerable<StudentPoints> StudentPointsList = new List<StudentPoints>(); 

    public void AddPerson(StudentPoints studentPoints) { 
     this.StudentPointsList.Add(studentPoints); 
    } 
} 

Однако, существует очевидная проблема с вышесказанным, который должен показать вам, почему анонимный класс является плохой идеей. Во-первых, вы отказались от словаря для более простого списка/IEnumerable. Это означает, что вы не можете получить доступ к значениям по ключу без «поиска в списке» (то есть у вас больше нет реализации хеш-таблицы). Во-вторых, вы, , по-прежнему обязаны менять типы при рефакторинге. Если вы не можете неявно преобразовывать один из другого типов, которые вы выключаете, вам все равно придется изменить конструкторы, которые вы используете в своем коде, чтобы создать StudentPoints. Несомненно, что изменение типа чего-то потребует изменений кода для большинства, если не всех ссылок на этот объект. Это именно то, для чего помогают инструменты рефакторинга в Visual Studio. Тем не менее, существует шаблон, который вы можете использовать, это C# и позволит вам, по крайней мере, «уменьшить» боль перехода, чтобы вам не приходилось находить каждый экземпляр в базе кода, прежде чем он будет компилироваться снова. Эта модель выглядит, как это и использует перегрузки + параметр [Obsolete], чтобы указать, что вы двигаетесь от старого типа и перехода к новому:

public class MyClass { 
    private Dictionary<int, double> StudentPoints = new Dictionary<int, double>(); //was string, double 

    [Obsolete] // unfixed code will call this 
    public void AddPerson(string studentId, double val) { 
     int studentIdInt; 
     if (Int32.TryParse(studentId, out studentIdInt) { 
      this.AddPerson(studentIdInt, val); 
      return; 
     } 

     throw new ArgumentException(nameof(studentId), "string not convertable to int"); 
    } 

    public void AddPerson(int studentId, double val) { 
     this.StudentList.Add(studentId, val); 
    } 
} 

Теперь компилятор предупредит вы вместо erroring при передаче string вместо. Проблемы здесь состоят в том, что теперь вы можете получить ошибку времени выполнения для любого string, который не может быть преобразован в int, что в противном случае было бы ошибкой времени компиляции. Кроме того, этот шаблон (перегрузка + устаревший атрибут) может использоваться даже с первым классом «reified» в качестве конструктора, но я хочу сказать, что вам не нужно , чтобы восстановить класс (на самом деле его бесполезный). Вместо этого вы должны понимать, что да, generics должны объявлять свои типы как можно точнее и Да, существуют шаблоны рефакторинга, которые существуют, чтобы вы могли быстро компилировать свой код после изменения типа для общего, но он поставляется с товаром от ошибок времени компиляции во время выполнения ошибок. Надеюсь, это поможет.

+0

Неужели это так плохо, чтобы иметь общедоступные классы? Если вложенный класс несколько специфичен для внешнего класса. – grek40

+0

@ paul23, ответ Эрика Липперта - это «как использовать (не злоупотреблять) дженерики». Этот ответ скорее является попыткой показать вам, почему вы не должны так беспокоиться о привязке к типу. –

+0

@ grek40, да, потому что любой неличный класс должен быть его собственным файлом. Единственное потенциальное исключение, которое я вижу, - это объявление частичного класса, но даже тогда желание выразить архитектуру кода настолько близко, насколько это возможно в структуре файла, приведет к тому, что класс станет собственным файлом. Очевидно, что частные классы прекрасны, потому что они используются только в классе, в котором они объявлены. –

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