简体   繁体   中英

runtime casting in C#?

I'm reading data from a custom data format that conceptually stores data in a table. Each column can have a distinct type. The types are specific to the file format and map to C# types.

I have a Column type that encapsulates the idea of a column, with generic parameter T indicating the C# type that is in the column. The Column.FormatType indicates the type in terms of the format types. So to read a value for a column, I have a simple method:

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

How simple and elegant! Now all I have to do is:

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

The above cast to type T would work in Java (albeit with a warning), and because of erasure the cast would not be evaluated until the value was actually used by the caller---at which point a ClassCastException would be thrown if the cast wasn't correct.

This doesn't work in C#. However, because C# doesn't throw away the generic types it should be possible to make it even better! I appears that I can ask for the type of T at runtime:

Type valueType=typeof(T);

Great---so I have the type of value that I'll be returning. What can I do with it? If this were Java, because there exists a Class.Cast method which performs a runtime cast, I would be home free! (Because each Java Class class has a generic type parameter indicating of the class is for it would also provide compile-time type safety.) The following is from my dream-world where C# Type class works like the Java Class class:

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

Obviously there is no Type.Cast()---so what do I do?

(Yes, I know there is a Convert.ChangeType() method, but that seems to perform conversions, not make a simple cast.)

Update: So it's seeming like this is simply not possible without boxing/unboxing using (T)(object)readInt(). But this is not acceptable. These files are really big---80MB, for example. Let's say I want to read an entire column of values. I'd have an elegant little method that uses generics and calls the method above like this:

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

Boxing/unboxing for millions of values? That doesn't sound good. I find it absurd that I'm going to have to throw away generics and resort to readColumnInt(), readColumnFloat(), etc. and reproduce all this code just to prevent boxing/unboxing!

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

This is pitiful. :(

return (T)(object)readInt();

I think the closest way to make this work is to overload readColumnInfo and not make it generic like so:

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

Why don't you implement your own casting operator from Column<T> to T ?

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

    private T value;
}

Then you can easily convert whenever you need to:

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

The short answer to all of this (see the question details) is that C# does not allow explicit casting to generic type T even if you know the type of T and you know the value that you have is T---unless you want to live with boxing/unboxing:

return (T)(object)myvalue;

This personally seems like a major deficiency in the language---there is nothing about the situation that says that boxing/unboxing would need to occur.

There is, however, a workaround, if you know ahead of time all the different types of T that are possible. Continuing the example in the question, we have a Column of generic type T representing tabular data in a file, and a parser that reads values from a column based upon the type of the column. I wanted the following in the parser:

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

As discussed, that doesn't work. But (assuming for this example that the parser is of type MyParser) you can actually create a different Column subclass for each T, like this:

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

Now I can update my parsing method to delegate to the column:

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

Note that the same program logic is occurring---it's just that by subclassing the generic column type, we've allowed specialization of a method to do the casting to T for us. In other words, we still have (T)readInt(), it's just that the (T) cast is happening, not within a single line, but in the override of the method that changes from:

  public abstract T readValue(MyParser myParser);

to

  public override int readValue(MyParser myParser)

So if the compiler can figure out how to cast to T in a method specialization, it should be able to figure it out on a single line cast. Put another way, nothing prevents C# from having a typeof(T).cast() method that would do exactly the same thing being done in method specialization above.

(What's even more frustrating about this whole exercise is that this solution has forced me to mix parsing code into the data object model, after trying so hard to keep it separate.)

Now, if somebody compiles this, looks at the generated CIL, and finds out that .NET is boxing/unboxing the return value just so that the specialized readValue() method can satisfy the generic return type T, I will cry.

Is the data stored in row-major or column-major order? If it's in row-major order, then having to scan the entire data set (millions of values you said) multiple times to pick out each column will dwarf the cost of boxing.

I really would suggest doing everything in one pass through the data, probably by building a vector of Action<string> (or Predicate<string> to report errors) delegates that process a single cell each into a List<T> associated with the column. Closed delegates could help a whole lot. Something like:

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
}

Of course you could separate the element parsing logic from the dataset walking logic, by choosing between alternate storeMap dictionaries for each format. And if you don't store things as strings, you could just as well use Predicate<byte[]> or similar.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM