views:

623

answers:

4

Based on http://alexreg.wordpress.com/2009/05/03/strongly-typed-csv-reader-in-c/, I created a DLL which can read different file types. I also have unit tests that run successfully. I create a struct and use it as the generic type.

Anyway, when I compile, I get a warning on each of the struct fields. For example: field 'FileReader.Tests.CsvReader.Record.Field1' is never assigned to, and will always have its default value 0

I am in fact setting the value with SetValueDirect() and when I run through the tests or debug the code, I can verify that. Why is it giving me that error then, and how can I avoid or fix it?

Here is some basic code to give you an idea. I'm guessing I haven't provided enough, but hopefully someone has a clue.

public abstract class FileReader<TRecord> : IDisposable where TRecord : struct
{
     public TRecord? ReadRecord()
     {
      List<string> fields;
      string rawData;

      this.recordNumber++;
      while (this.ReadRecord(this.fieldTypeInfoList.Length, out fields, out rawData))
      {
       try
       {
        // Insert the current record number to the beginning of the field list
        fields.Insert(0, this.recordNumber.ToString(CultureInfo.InvariantCulture));

        // Convert each field to its correct type and set the value
        TRecord record = new TRecord();
        FieldTypeInfo fieldTypeInfo;
        object fieldValue;

        // Loop through each field
        for (int i = 0; i < this.fieldTypeInfoList.Length; i++)
        {
         fieldTypeInfo = this.fieldTypeInfoList[i];

         bool allowNull = fieldTypeInfo.AllowNull == null ? this.AllowNull : fieldTypeInfo.AllowNull.Value;
         if (i >= fields.Count && !allowNull)
         {
          // There are no field values for the current field
          throw new ParseException("Field is missing", this.RecordNumber, fieldTypeInfo, rawData);
         }
         else
         {
          // Trim the field value
          bool trimSpaces = fieldTypeInfo.TrimSpaces == null ? this.TrimSpaces : fieldTypeInfo.TrimSpaces.Value;
          if (trimSpaces)
          {
           fields[i] = fields[i].Trim();
          }

          if (fields[i].Length == 0 && !allowNull)
          {
           throw new ParseException("Field is null", this.RecordNumber, fieldTypeInfo, rawData);
          }

          try
          {
           fieldValue = fieldTypeInfo.TypeConverter.ConvertFromString(fields[i]);
          }
          catch (Exception ex)
          {
           throw new ParseException("Could not convert field value", ex, this.RecordNumber, fieldTypeInfo, rawData);
          }

          fieldTypeInfo.FieldInfo.SetValueDirect(__makeref(record), fieldValue);
         }
        }

        return record;
       }
       catch (ParseException ex)
       {
        ParseErrorAction action = (ex.FieldTypeInfo.ParseError == null) ? DefaultParseErrorAction : ex.FieldTypeInfo.ParseError.Value;

        switch (action)
        {
         case ParseErrorAction.SkipRecord:
          continue;

         case ParseErrorAction.ThrowException:
          throw;

         case ParseErrorAction.RaiseEvent:
          throw new NotImplementedException("Events are not yet available", ex);

         default:
          throw new NotImplementedException("Unknown ParseErrorAction", ex);
        }
       }
      }

      return null;
     }
}
+1  A: 

It seems that the compiler is not capable of detecting such "indirect" assignments. It can only detect direct assignments like field=value.

You can anyway disable specific compiler warnings. Assuming that you are using Visual Studio, see here: http://msdn.microsoft.com/en-us/library/edzzzth4.aspx

Konamiman
I would agree with you except these warnings were not coming up early on. At some point (I wish I knew when!), something I changed "broke" it. I'm going to see if I can somehow narrow that down.
Nelson
+3  A: 

The compiler is never going to be able to spot reflection. By definition, by using reflection you have stepped outside the compiler.

IMO, though, this is a bad use of structs - that looks very much like it should be working on classes...

Marc Gravell
What do you mean with "it should be working on classes"? The struct strictly defines the record fields and field attributes. Maybe I should create a Record class with record.AddField("name", typeof(int)) or something similar, but I do like the simplicity and strong typing of this.
Nelson
Anyway, you do bring up a good point about Reflection. I am confused then why I wasn't getting that error earlier...
Nelson
structs aren't generally meant to represent "records". They are meant to represent values - things like "26 GBP" etc. "Records" are classically objects = classes. Don't be misled; oversized structs can **introduce** inefficiencies.
Marc Gravell
I'm guessing the inefficiencies are due to the fact that structs are value types instead of reference types? Also that Reflection isn't particularly fast? I'll have to think about this some more. Thanks for all the input. Anyway, I found out how to get rid of the warnings. If the struct is declared private or internal, you get the warnings. If it's declared public, no warnings. I'm guessing if it's public the compiler assumes it could me modified externally and it has no way of checking that.
Nelson
Indeed - value types will get **copied** a lot; leading to both stack bloat and lost updates. Reflection can be sped up - that isn't a huge problem. I think your last assumption is valid.
Marc Gravell
(sped up, for example, by `DynamicMethod`/`Expression` etc - or for props `Delegate.CreateDelegate`/HyperDescriptor)
Marc Gravell
I was thinking it's no big deal with copying since the reader doesn't pass it around. But I AM passing it around afterward... I understand how value/reference types work so I completely agree with you. I should be able to replace the struct with a class fairly easily and then maybe use an indexer [] to get/set each field value. That should get rid of reflection and my original problem with the warnings. I would like to avoid boxing/unboxing (which the struct does avoid, BUT it does get copied which is worse), so I'll have to think about it some more. Thanks again.
Nelson
A: 

I hate when I reinvent the wheel. FileHelpers from http://www.filehelpers.com/ already does this in a very similar way and of course covers more edge cases. They have you define a class with attributes instead of a struct (as Marc suggested). If you set their record definition as non-public, you get the same compiler warnings I was getting.

Nelson
Now that I'm using it some more, I must say that their CSV implementation (as seen by the end user) is not as clean or easy to use as mine. Either way, they've done a great job and probably better than I would ever do.
Nelson
A: 

If using struct instead of class you should know why you do it.

Some (rare) cases where you should use struct:

  1. P/Invoke (when the native function uses "structs") - this is (imho) one of the the reason why MS added struct to .NET (in order to be compatible with native code)
  2. structs are so called value types. structs should be very small (e.g. struct DateTime, struct GpsCoordinates are reasons using struct instead of classes since struct are "faster" than classes because the GC need not to care about it
  3. If you don't know what to use: use class!!

--hfrmobile

hfrmobile