литье во время выполнения в C #? - PullRequest
5 голосов
/ 25 сентября 2010

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

У меня есть тип Column, который воплощает идею столбца, с общим параметром T, указывающим тип C #, который находится в столбце. Column.FormatType указывает тип в терминах типов формата. Таким образом, чтобы прочитать значение для столбца, у меня есть простой метод:

protected T readColumnValue<T>(Column<T> column)
{
  switch (column.FormatType)
  {
    case FormatType.Int:
      return (T)readInt();
  }
}

Как просто и элегантно! Теперь все, что мне нужно сделать, это:

Column<int> column=new Column<int>(...)
int value=readColumnValue(column);

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

Это не работает в C #. Однако, поскольку C # не выбрасывает универсальные типы, должно быть возможно сделать его еще лучше! Похоже, я могу запросить тип T во время выполнения:

Type valueType=typeof(T);

Отлично --- поэтому у меня есть тип значения, которое я буду возвращать. Что я могу с этим сделать? Если бы это была Java, потому что существует метод Class.Cast, который выполняет приведение во время выполнения, я был бы свободен дома! (Поскольку каждый класс Java-класса имеет параметр общего типа, указывающий на класс, он также обеспечивает безопасность типов во время компиляции.) Следующее из моей мечты, где класс C # Type работает подобно классу Java-класса:

protected T readColumnValue<T>(Column<T> column)
{
  Type<T> valueType=typeof(T);
  switch (column.FormatType)
  {
    case FormatType.Int:
      return valueType.Cast(readInt());
  }
}

Очевидно, что Type.Cast () не существует --- так что мне делать?

(Да, я знаю, что есть метод Convert.ChangeType (), но, похоже, он выполняет преобразования, а не выполняет простое приведение.)

Обновление: похоже, это просто невозможно без упаковки / распаковки с использованием (T) (object) readInt (). Но это не приемлемо. Эти файлы действительно большие - например, 80 МБ. Допустим, я хочу прочитать весь столбец значений. У меня был бы элегантный маленький метод, который использует дженерики и вызывает метод выше, как этот:

public T[] readColumn<T>(Column<T> column, int rowStart, int rowEnd, T[] values)
{
  ...  //seek to column start
  for (int row = rowStart; row < rowEnd; ++row)
  {
    values[row - rowStart] = readColumnValue(column);
    ... //seek to next row

Бокс / распаковка для миллионов ценностей? Это не звучит хорошо. Я нахожу абсурдным, что мне придется выбросить дженерики и прибегнуть к readColumnInt (), readColumnFloat () и т. Д. И воспроизвести весь этот код только для предотвращения упаковки / распаковки!

public int[] readColumnInt(Column<int> column, int rowStart, int rowEnd, int[] values)
{
  ...  //seek to column start
  for (int row = rowStart; row < rowEnd; ++row)
  {
    values[row - rowStart] = readInt();
    ... //seek to next row

public float[] readColumnFloat(Column<float> column, int rowStart, int rowEnd, float[] values)
{
  ...  //seek to column start
  for (int row = rowStart; row < rowEnd; ++row)
  {
    values[row - rowStart] = readFloat();
    ... //seek to next row

Это жалко. (

Ответы [ 5 ]

2 голосов
/ 25 сентября 2010
return (T)(object)readInt();
1 голос
/ 01 октября 2010

Почему бы вам не реализовать свой собственный оператор приведения от Column<T> до T?

public class Column<T>
{
    public static explicit operator T(Column<T> value)
    {
        return value;
    }

    private T value;
}

Тогда вы можете легко конвертировать, когда вам нужно:

Column<int> column = new Column<int>(...)
int value = (int)column;
1 голос
/ 25 сентября 2010

Я думаю, что самый близкий способ сделать эту работу - это перегрузить readColumnInfo, а не сделать его универсальным, например, так:

    protected Int32 readColumnValue(Column<Int32> column) {
        return readInt();
    }
    protected Int64 readColumnValue(Column<Int64> column) {
        return readLong();
    }
    protected String readColumnValue(Column<String> column){
        return String.Empty;
    }
0 голосов
/ 01 октября 2010

Сохраняются ли данные в мажорном порядке или в мажорном столбце?Если это в главном порядке строк, то необходимость сканировать весь набор данных (миллионы значений, которые вы сказали) несколько раз, чтобы выделить каждый столбец, приведет к снижению стоимости бокса.

Я действительно предложил бы сделать все водин проход по данным, вероятно, путем построения вектора из Action<string> (или Predicate<string> для сообщения об ошибках) делегатов, каждый из которых обрабатывает одну ячейку в List<T>, связанный со столбцом.Закрытые делегаты могут сильно помочь.Что-то вроде:

public class TableParser
{
    private static bool Store(List<string> lst, string cell) { lst.Append(cell); return true; }
    private static bool Store(List<int> lst, string cell) { int val; if (!int.TryParse(cell, out val)) return false; lst.Append(val); return true; }
    private static bool Store(List<double> lst, string cell) { double val; if (!double.TryParse(cell, out val)) return false; lst.Append(val); return true; }
    private static readonly Dictionary<Type, System.Reflection.MethodInfo> storeMap = new Dictionary<Type, System.Reflection.MethodInfo>();

    static TableParser()
    {
        System.Reflection.MethodInfo[] storeMethods = typeof(TableParser).GetMethods("Store", BindingFlags.Private | BindingFlags.Static);
        foreach (System.Reflection.MethodInfo mi in storeMethods)
            storeMap[mi.GetParameters()[0].GetGenericParameters()[0]] = mi;
    }

    private readonly List< Predicate<string> > columnHandlers = new List< Predicate<string> >;

    public bool TryBindColumn<T>(List<T> lst)
    {
        System.Reflection.MethodInfo storeImpl;
        if (!storeMap.TryGetValue(typeof(T), out storeImpl)) return false;
        columnHandlers.Add(Delegate.Create(typeof(Predicate<string>), storeImpl, lst));
        return true;
    }

    // adapt your existing logic to grab a row, pull it apart with string.Split or whatever, and walk through columnHandlers passing in each of the pieces
}

Конечно, вы можете отделить логику разбора элемента от логики обхода набора данных, выбрав альтернативные storeMap словари для каждого формата.И если вы не храните вещи как строки, вы также можете использовать Predicate<byte[]> или аналогичный.

0 голосов
/ 01 октября 2010

Короткий ответ на все это (см. Подробности вопроса) состоит в том, что C # не позволяет явное приведение к универсальному типу T, даже если вы знаете тип T и знаете, что у вас есть значение T ---, если только Вы хотите жить с боксом / распаковкой:

return (T)(object)myvalue;

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

Однако существует обходной путь, если вы заранее знаете все возможные типы T. Продолжая пример в вопросе, у нас есть столбец универсального типа T, представляющий табличные данные в файле, и анализатор, который считывает значения из столбца на основе типа столбца. Я хотел следующее в парсере:

protected T readColumnValue<T>(Column<T> column)
{
  switch (column.FormatType)
  {
    case FormatType.Int:
      return (T)readInt();
  }
}

Как уже говорилось, это не работает. Но (при условии, что для этого примера синтаксический анализатор имеет тип MyParser), вы можете создать отдельный подкласс Column для каждого T, например, так:

public abstract class Column<T>
{
  public abstract T readValue(MyParser myParser);
}

public class IntColumn : Column<int>
{
  public override int readValue(MyParser myParser)
  {
    return myParser.readInt();
  }
}

Теперь я могу обновить свой метод анализа, чтобы делегировать его в столбец:

protected T readColumnValue<T>(Column<T> column)
{
  return column.readValue(this);
}

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

  public abstract T readValue(MyParser myParser);

до

  public override int readValue(MyParser myParser)

Так что, если компилятор может выяснить, как приводить к типу T в специализации метода, он должен иметь возможность вычислить это в одной строке приведения. Другими словами, ничто не мешает C # иметь метод typeof (T) .cast (), который будет делать то же самое, что и в описанной выше специализации метода.

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

Теперь, если кто-то скомпилирует это, посмотрит на сгенерированный CIL и обнаружит, что .NET упаковывает / распаковывает возвращаемое значение только для того, чтобы специализированный метод readValue () мог удовлетворить универсальный тип возвращаемого значения T, я заплачу.

...