Permalink
Browse files

Created non-generic overloads for reading, writing, and attribute map…

…ping.
  • Loading branch information...
1 parent 76207e1 commit 8335cb31783e964cf39446b78fdd61d1b4b76abb @JoshClose committed Jul 26, 2012
View
1 lib/repositories.config
@@ -2,4 +2,5 @@
<repositories>
<repository path="..\src\CsvHelper.Tests\packages.config" />
<repository path="..\src\CsvHelper\packages.config" />
+ <repository path="..\src\CsvHelper35.Tests\packages.config" />
</repositories>
View
BIN lib/xunit.1.9.1/lib/net20/xunit.dll
Binary file not shown.
View
5 lib/xunit.1.9.1/lib/net20/xunit.dll.tdnet
@@ -0,0 +1,5 @@
+<TestRunner>
+ <FriendlyName>xUnit.net {0}.{1}.{2} build {3}</FriendlyName>
+ <AssemblyPath>xunit.runner.tdnet.dll</AssemblyPath>
+ <TypeName>Xunit.Runner.TdNet.TdNetRunner</TypeName>
+</TestRunner>
View
BIN lib/xunit.1.9.1/lib/net20/xunit.runner.msbuild.dll
Binary file not shown.
View
BIN lib/xunit.1.9.1/lib/net20/xunit.runner.tdnet.dll
Binary file not shown.
View
BIN lib/xunit.1.9.1/lib/net20/xunit.runner.utility.dll
Binary file not shown.
View
2,611 lib/xunit.1.9.1/lib/net20/xunit.xml
2,611 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
View
BIN lib/xunit.1.9.1/xunit.1.9.1.nupkg
Binary file not shown.
View
90 src/CsvHelper.Tests/CsvReaderTests.cs
@@ -263,7 +263,7 @@ public void GetRecordWithDuplicateHeaderFields()
}
[Fact]
- public void GetRecordTest()
+ public void GetRecordGenericTest()
{
var headerData = new []
{
@@ -304,7 +304,48 @@ public void GetRecordTest()
}
[Fact]
- public void GetRecordsTest()
+ public void GetRecordTest()
+ {
+ var headerData = new[]
+ {
+ "IntColumn",
+ "String Column",
+ "GuidColumn",
+ };
+ var recordData = new[]
+ {
+ "1",
+ "string column",
+ Guid.NewGuid().ToString(),
+ };
+ var isHeaderRecord = true;
+ var mockFactory = new MockRepository( MockBehavior.Default );
+ var csvParserMock = mockFactory.Create<ICsvParser>();
+ csvParserMock.Setup( m => m.Configuration ).Returns( new CsvConfiguration() );
+ csvParserMock.Setup( m => m.Read() ).Returns( () =>
+ {
+ if( isHeaderRecord )
+ {
+ isHeaderRecord = false;
+ return headerData;
+ }
+ return recordData;
+ } );
+
+ var csv = new CsvReader( csvParserMock.Object );
+ csv.Configuration.IsStrictMode = false;
+ csv.Read();
+ var record = (TestRecord)csv.GetRecord( typeof( TestRecord ));
+
+ Assert.Equal( Convert.ToInt32( recordData[0] ), record.IntColumn );
+ Assert.Equal( recordData[1], record.StringColumn );
+ Assert.Equal( "test", record.TypeConvertedColumn );
+ Assert.Equal( Convert.ToInt32( recordData[0] ), record.FirstColumn );
+ Assert.Equal( new Guid( recordData[2] ), record.GuidColumn );
+ }
+
+ [Fact]
+ public void GetRecordsGenericTest()
{
var headerData = new []
{
@@ -349,6 +390,51 @@ public void GetRecordsTest()
}
[Fact]
+ public void GetRecordsTest()
+ {
+ var headerData = new[]
+ {
+ "IntColumn",
+ "String Column",
+ "GuidColumn",
+ };
+ var count = -1;
+ var mockFactory = new MockRepository( MockBehavior.Default );
+ var csvParserMock = mockFactory.Create<ICsvParser>();
+ csvParserMock.Setup( m => m.Configuration ).Returns( new CsvConfiguration() );
+ var guid = Guid.NewGuid();
+ csvParserMock.Setup( m => m.Read() ).Returns( () =>
+ {
+ count++;
+ if( count == 0 )
+ {
+ return headerData;
+ }
+ if( count > 2 )
+ {
+ return null;
+ }
+ return new[] { count.ToString(), "string column " + count, guid.ToString() };
+ } );
+
+ var csv = new CsvReader( csvParserMock.Object );
+ csv.Configuration.IsStrictMode = false;
+ var records = csv.GetRecords( typeof( TestRecord ) ).ToList();
+
+ Assert.Equal( 2, records.Count );
+
+ for( var i = 1; i <= records.Count; i++ )
+ {
+ var record = (TestRecord)records[i - 1];
+ Assert.Equal( i, record.IntColumn );
+ Assert.Equal( "string column " + i, record.StringColumn );
+ Assert.Equal( "test", record.TypeConvertedColumn );
+ Assert.Equal( i, record.FirstColumn );
+ Assert.Equal( guid, record.GuidColumn );
+ }
+ }
+
+ [Fact]
public void GetRecordsWithDuplicateHeaderNames()
{
var headerData = new[]
View
13 src/CsvHelper/Configuration/CsvConfiguration.cs
@@ -279,7 +279,18 @@ public virtual void ClassMapping( CsvClassMap classMap )
/// <typeparam name="TClass">The type of custom class that contains the attributes.</typeparam>
public virtual void AttributeMapping<TClass>() where TClass : class
{
- var props = typeof( TClass ).GetProperties( PropertyBindingFlags );
+ AttributeMapping( typeof( TClass ) );
+ }
+
+ /// <summary>
+ /// Use <see cref="CsvFieldAttribute"/>s to configure mappings.
+ /// All properties are mapped by default and attribute mapping
+ /// will change the default property behavior.
+ /// </summary>
+ /// <param name="type">The type of custom class that contains the attributes.</param>
+ public virtual void AttributeMapping( Type type )
+ {
+ var props = type.GetProperties( PropertyBindingFlags );
foreach( var property in props )
{
var csvFieldAttribute = ReflectionHelper.GetAttribute<CsvFieldAttribute>( property, true );
View
193 src/CsvHelper/CsvReader.cs
@@ -513,6 +513,19 @@ public virtual bool TryGetField<T>( string name, int index, TypeConverter conver
}
/// <summary>
+ /// Gets the record.
+ /// </summary>
+ /// <param name="type">The <see cref="Type"/> of the record.</param>
+ /// <returns>The record.</returns>
+ public virtual object GetRecord( Type type )
+ {
+ CheckDisposed();
+ CheckHasBeenRead();
+
+ return GetReadRecordFunc( type )( this );
+ }
+
+ /// <summary>
/// Gets all the records in the CSV file and
/// converts each to <see cref="Type"/> T. The Read method
/// should not be used when using this.
@@ -522,6 +535,8 @@ public virtual bool TryGetField<T>( string name, int index, TypeConverter conver
public virtual IEnumerable<T> GetRecords<T>() where T : class
{
CheckDisposed();
+ // Don't need to check if it's been read
+ // since we're doing the reading ourselves.
while( Read() )
{
@@ -530,6 +545,25 @@ public virtual bool TryGetField<T>( string name, int index, TypeConverter conver
}
/// <summary>
+ /// Gets all the records in the CSV file and
+ /// converts each to <see cref="Type"/> T. The Read method
+ /// should not be used when using this.
+ /// </summary>
+ /// <param name="type">The <see cref="Type"/> of the record.</param>
+ /// <returns>An <see cref="IList{Object}" /> of records.</returns>
+ public virtual IEnumerable<object> GetRecords( Type type )
+ {
+ CheckDisposed();
+ // Don't need to check if it's been read
+ // since we're doing the reading ourselves.
+
+ while( Read() )
+ {
+ yield return GetReadRecordFunc( type )( this );
+ }
+ }
+
+ /// <summary>
/// Invalidates the record cache for the given type. After <see cref="ICsvReader.GetRecord{T}"/> is called the
/// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
/// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
@@ -541,6 +575,19 @@ public virtual bool TryGetField<T>( string name, int index, TypeConverter conver
recordFuncs.Remove( typeof( T ) );
configuration.Properties.Clear();
}
+
+ /// <summary>
+ /// Invalidates the record cache for the given type. After <see cref="ICsvReader.GetRecord{T}"/> is called the
+ /// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
+ /// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
+ /// changes, <see cref="ICsvReader.InvalidateRecordCache{T}"/> needs to be called to updated the
+ /// record cache.
+ /// </summary>
+ /// <param name="type">The type to invalidate.</param>
+ public virtual void InvalidateRecordCache( Type type )
+ {
+ recordFuncs.Remove( type );
+ }
#endif
/// <summary>
@@ -707,71 +754,101 @@ protected virtual void ParseNamedIndexes()
protected virtual Func<CsvReader, T> GetReadRecordFunc<T>() where T : class
{
var recordType = typeof( T );
- if( !recordFuncs.ContainsKey( recordType ) )
+ CreateReadRecordFunc( recordType, ( body, readerParameter ) => Expression.Lambda<Func<CsvReader, T>>( body, readerParameter ).Compile() );
+
+ return (Func<CsvReader, T>)recordFuncs[recordType];
+ }
+
+ /// <summary>
+ /// Gets the function delegate used to populate
+ /// a custom class object with data from the reader.
+ /// </summary>
+ /// <param name="recordType">The <see cref="Type"/> of object that is created
+ /// and populated.</param>
+ /// <returns>The function delegate.</returns>
+ protected virtual Func<CsvReader, object> GetReadRecordFunc( Type recordType )
+ {
+ CreateReadRecordFunc( recordType, ( body, readerParameter ) => Expression.Lambda<Func<CsvReader, object>>( body, readerParameter ).Compile() );
+
+ return (Func<CsvReader, object>)recordFuncs[recordType];
+ }
+
+ /// <summary>
+ /// Creates the read record func for the given type if it
+ /// doesn't already exist.
+ /// </summary>
+ /// <param name="recordType">Type of the record.</param>
+ /// <param name="expressionCompiler">The expression compiler.</param>
+ protected virtual void CreateReadRecordFunc( Type recordType, Func<Expression, ParameterExpression, Delegate> expressionCompiler )
+ {
+ if( recordFuncs.ContainsKey( recordType ) )
{
- var bindings = new List<MemberBinding>();
- var readerParameter = Expression.Parameter( GetType(), "reader" );
+ return;
+ }
- // If there is no property mappings yet, use attribute mappings.
- if( configuration.Properties.Count == 0 )
- {
- configuration.AttributeMapping<T>();
- }
+ var bindings = new List<MemberBinding>();
+ var readerParameter = Expression.Parameter( GetType(), "reader" );
- AddPropertyBindings( readerParameter, configuration.Properties, bindings );
+ // If there is no property mappings yet, use attribute mappings.
+ if( configuration.Properties.Count == 0 )
+ {
+ configuration.AttributeMapping( recordType );
+ }
- foreach( var referenceMap in configuration.References )
- {
- var referenceReaderParameter = Expression.Parameter( GetType(), "reader2" );
- var referenceBindings = new List<MemberBinding>();
- AddPropertyBindings( referenceReaderParameter, referenceMap.ReferenceProperties, referenceBindings );
- var referenceBody = Expression.MemberInit( Expression.New( referenceMap.Property.PropertyType ), referenceBindings );
- var referenceFunc = Expression.Lambda( referenceBody, referenceReaderParameter );
- var referenceCompiled = referenceFunc.Compile();
- var referenceCompiledMethod = referenceCompiled.GetType().GetMethod( "Invoke" );
- Expression referenceObjectExpression = Expression.Call( Expression.Constant( referenceCompiled ), referenceCompiledMethod, Expression.Constant( this ) );
- bindings.Add( Expression.Bind( referenceMap.Property, referenceObjectExpression ) );
- }
+ AddPropertyBindings( readerParameter, configuration.Properties, bindings );
- var body = Expression.MemberInit( Expression.New( recordType ), bindings );
- var func = Expression.Lambda<Func<CsvReader, T>>( body, readerParameter ).Compile();
- recordFuncs[recordType] = func;
-
- // This is the expression that is built:
- //
- // Func<CsvReader, T> func = reader =>
- // {
- // foreach( var propertyMap in configuration.Properties )
- // {
- // string field = reader[index];
- // object converted = TypeConverter.ConvertFromString( field );
- // T convertedAsType = converted as T;
- // property.Property = convertedAsType;
- // }
- //
- // foreach( var referenceMap in configuration.References )
- // {
- // Func<CsvReader, referenceMap.Property.PropertyType> func2 = reader2 =>
- // {
- // foreach( var property in referenceMap.ReferenceProperties )
- // {
- // string field = reader[index];
- // object converted = TypeConverter.ConvertFromString( field );
- // T convertedAsType = converted as T;
- // property.Property = convertedAsType;
- // }
- // };
- // reference.Property = func2( (CsvReader)this );
- // }
- // };
- //
- // The func can then be called:
- //
- // func( CsvReader reader );
- //
+ foreach( var referenceMap in configuration.References )
+ {
+ var referenceReaderParameter = Expression.Parameter( GetType(), "reader2" );
+ var referenceBindings = new List<MemberBinding>();
+ AddPropertyBindings( referenceReaderParameter, referenceMap.ReferenceProperties, referenceBindings );
+ var referenceBody = Expression.MemberInit( Expression.New( referenceMap.Property.PropertyType ), referenceBindings );
+ var referenceFunc = Expression.Lambda( referenceBody, referenceReaderParameter );
+ var referenceCompiled = referenceFunc.Compile();
+ var referenceCompiledMethod = referenceCompiled.GetType().GetMethod( "Invoke" );
+ Expression referenceObjectExpression = Expression.Call( Expression.Constant( referenceCompiled ), referenceCompiledMethod, Expression.Constant( this ) );
+ bindings.Add( Expression.Bind( referenceMap.Property, referenceObjectExpression ) );
}
- return (Func<CsvReader, T>)recordFuncs[recordType];
+ var body = Expression.MemberInit( Expression.New( recordType ), bindings );
+ var func = expressionCompiler( body, readerParameter );
+ recordFuncs[recordType] = func;
+
+ #region This is the expression that is built:
+
+ //
+ // Func<CsvReader, T> func = reader =>
+ // {
+ // foreach( var propertyMap in configuration.Properties )
+ // {
+ // string field = reader[index];
+ // object converted = TypeConverter.ConvertFromString( field );
+ // T convertedAsType = converted as T;
+ // property.Property = convertedAsType;
+ // }
+ //
+ // foreach( var referenceMap in configuration.References )
+ // {
+ // Func<CsvReader, referenceMap.Property.PropertyType> func2 = reader2 =>
+ // {
+ // foreach( var property in referenceMap.ReferenceProperties )
+ // {
+ // string field = reader[index];
+ // object converted = TypeConverter.ConvertFromString( field );
+ // T convertedAsType = converted as T;
+ // property.Property = convertedAsType;
+ // }
+ // };
+ // reference.Property = func2( (CsvReader)this );
+ // }
+ // };
+ //
+ // The func can then be called:
+ //
+ // func( CsvReader reader );
+ //
+
+ #endregion
}
/// <summary>
View
257 src/CsvHelper/CsvWriter.cs
@@ -3,6 +3,7 @@
// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html
// http://csvhelper.com
using System;
+using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
@@ -186,6 +187,25 @@ public virtual void NextRecord()
}
/// <summary>
+ /// Writes the record to the CSV file.
+ /// </summary>
+ /// <param name="type">The type of the record.</param>
+ /// <param name="record">The record to write.</param>
+ public virtual void WriteRecord( Type type, object record )
+ {
+ CheckDisposed();
+
+ if( configuration.HasHeaderRecord && !hasHeaderBeenWritten )
+ {
+ WriteHeader( type );
+ }
+
+ GetWriteRecordAction( type ).DynamicInvoke( this, record );
+
+ NextRecord();
+ }
+
+ /// <summary>
/// Writes the list of records to the CSV file.
/// </summary>
/// <typeparam name="T">The type of the record.</typeparam>
@@ -207,6 +227,27 @@ public virtual void NextRecord()
}
/// <summary>
+ /// Writes the list of records to the CSV file.
+ /// </summary>
+ /// <param name="type">The type of the record.</param>
+ /// <param name="records">The list of records to write.</param>
+ public virtual void WriteRecords( Type type, IEnumerable<object> records )
+ {
+ CheckDisposed();
+
+ if( configuration.HasHeaderRecord && !hasHeaderBeenWritten )
+ {
+ WriteHeader( type );
+ }
+
+ foreach( var record in records )
+ {
+ GetWriteRecordAction( type ).DynamicInvoke( this, record );
+ NextRecord();
+ }
+ }
+
+ /// <summary>
/// Invalidates the record cache for the given type. After <see cref="ICsvWriter.WriteRecord{T}"/> is called the
/// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
/// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
@@ -218,6 +259,18 @@ public virtual void NextRecord()
typeActions.Remove( typeof( T ) );
configuration.Properties.Clear();
}
+
+ /// <summary>
+ /// Invalidates the record cache for the given type. After <see cref="ICsvWriter.WriteRecord{T}"/> is called the
+ /// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
+ /// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
+ /// changes, <see cref="ICsvWriter.InvalidateRecordCache"/> needs to be called to updated the
+ /// record cache.
+ /// </summary>
+ public virtual void InvalidateRecordCache( Type type )
+ {
+ typeActions.Remove( type );
+ }
#endif
/// <summary>
@@ -267,11 +320,21 @@ protected virtual void CheckDisposed()
/// <summary>
/// Writes the header record from the given properties.
/// </summary>
+ /// <typeparam name="T">The type of the record.</typeparam>
protected virtual void WriteHeader<T>() where T : class
{
+ WriteHeader( typeof( T ) );
+ }
+
+ /// <summary>
+ /// Writes the header record from the given properties.
+ /// </summary>
+ /// <param name="type">The type of the record.</param>
+ protected virtual void WriteHeader( Type type )
+ {
if( configuration.Properties.Count == 0 )
{
- configuration.AttributeMapping<T>();
+ configuration.AttributeMapping( type );
}
var properties = new CsvPropertyMapCollection();
@@ -302,88 +365,73 @@ protected virtual void CheckDisposed()
/// <returns>The action delegate.</returns>
protected virtual Action<CsvWriter, T> GetWriteRecordAction<T>() where T : class
{
- var type = typeof(T);
+ var type = typeof( T );
- if (!typeActions.ContainsKey(type))
+ if( !typeActions.ContainsKey( type ) )
{
Action<CsvWriter, T> func = null;
- var writerParameter = Expression.Parameter(typeof(CsvWriter), "writer");
- var recordParameter = Expression.Parameter(typeof(T), "record");
+ var writerParameter = Expression.Parameter( typeof( CsvWriter ), "writer" );
+ var recordParameter = Expression.Parameter( typeof( T ), "record" );
- if (configuration.Properties.Count == 0)
+ if( configuration.Properties.Count == 0 )
{
configuration.AttributeMapping<T>();
}
- foreach (var propertyMap in configuration.Properties)
+ foreach( var propertyMap in configuration.Properties )
{
- if (propertyMap.IgnoreValue)
+ if( propertyMap.IgnoreValue )
{
// Skip ignored properties.
continue;
}
- if (propertyMap.TypeConverterValue == null || !propertyMap.TypeConverterValue.CanConvertTo(typeof(string)))
+ if( propertyMap.TypeConverterValue == null || !propertyMap.TypeConverterValue.CanConvertTo( typeof( string ) ) )
{
// Skip if the type isn't convertible.
continue;
}
- Expression fieldExpression = Expression.Property(recordParameter, propertyMap.PropertyValue);
- var typeConverterExpression = Expression.Constant(propertyMap.TypeConverterValue);
+ Expression fieldExpression = Expression.Property( recordParameter, propertyMap.PropertyValue );
+ var typeConverterExpression = Expression.Constant( propertyMap.TypeConverterValue );
var convertMethod = Configuration.UseInvariantCulture ? "ConvertToInvariantString" : "ConvertToString";
- var method = propertyMap.TypeConverterValue.GetType().GetMethod(convertMethod, new[] { typeof(object) });
- fieldExpression = Expression.Convert(fieldExpression, typeof(object));
- fieldExpression = Expression.Call(typeConverterExpression, method, fieldExpression);
+ var method = propertyMap.TypeConverterValue.GetType().GetMethod( convertMethod, new[] { typeof( object ) } );
+ fieldExpression = Expression.Convert( fieldExpression, typeof( object ) );
+ fieldExpression = Expression.Call( typeConverterExpression, method, fieldExpression );
- var areEqualExpression = Expression.Equal(recordParameter, Expression.Constant(null));
- fieldExpression = Expression.Condition(areEqualExpression, Expression.Constant(string.Empty), fieldExpression);
+ var areEqualExpression = Expression.Equal( recordParameter, Expression.Constant( null ) );
+ fieldExpression = Expression.Condition( areEqualExpression, Expression.Constant( string.Empty ), fieldExpression );
- var body = Expression.Call(writerParameter, "WriteField", new[] { typeof(string) }, fieldExpression);
- func += Expression.Lambda<Action<CsvWriter, T>>(body, writerParameter, recordParameter).Compile();
+ var body = Expression.Call( writerParameter, "WriteField", new[] { typeof( string ) }, fieldExpression );
+ func += Expression.Lambda<Action<CsvWriter, T>>( body, writerParameter, recordParameter ).Compile();
}
typeActions[type] = func;
}
return (Action<CsvWriter, T>)typeActions[type];
}
-#elif !NET_2_0
+
/// <summary>
/// Gets the action delegate used to write the custom
/// class object to the writer.
/// </summary>
- /// <typeparam name="T">The type of the custom class being written.</typeparam>
+ /// <param name="type">The type of the custom class being written.</param>
/// <returns>The action delegate.</returns>
- protected virtual Action<CsvWriter, T> GetWriteRecordAction<T>() where T : class
+ protected virtual Delegate GetWriteRecordAction( Type type )
{
- var type = typeof( T );
-
if( !typeActions.ContainsKey( type ) )
{
- Action<CsvWriter, T> func = null;
+ var delegates = new List<Delegate>();
var writerParameter = Expression.Parameter( typeof( CsvWriter ), "writer" );
- var recordParameter = Expression.Parameter( typeof( T ), "record" );
+ var recordParameter = Expression.Parameter( type, "record" );
if( configuration.Properties.Count == 0 )
{
- configuration.AttributeMapping<T>();
- }
-
- // Get a list of all the properties so they will
- // be sorted properly.
- var properties = new CsvPropertyMapCollection();
- properties.AddRange( configuration.Properties );
- foreach( var reference in configuration.References )
- {
- properties.AddRange( reference.ReferenceProperties );
+ configuration.AttributeMapping( type );
}
- // A list of expressions that will go inside
- // the lambda code block.
- var expressions = new List<Expression>();
-
- foreach( var propertyMap in properties )
+ foreach( var propertyMap in configuration.Properties )
{
if( propertyMap.IgnoreValue )
{
@@ -397,44 +445,125 @@ protected virtual void CheckDisposed()
continue;
}
- // So we don't have to access a modified closure.
- var propertyMapCopy = propertyMap;
-
- // Get the reference this property is a part of.
- var reference = ( from r in configuration.References
- from p in r.ReferenceProperties
- where p == propertyMapCopy
- select r ).SingleOrDefault();
-
- // Get the current object. Either the param passed in
- // or a reference property object.
- var currentRecordObject =
- reference == null
- ? (Expression)recordParameter
- : Expression.Property( recordParameter, reference.Property );
-
- Expression fieldExpression = Expression.Property( currentRecordObject, propertyMap.PropertyValue );
+ Expression fieldExpression = Expression.Property( recordParameter, propertyMap.PropertyValue );
var typeConverterExpression = Expression.Constant( propertyMap.TypeConverterValue );
var convertMethod = Configuration.UseInvariantCulture ? "ConvertToInvariantString" : "ConvertToString";
var method = propertyMap.TypeConverterValue.GetType().GetMethod( convertMethod, new[] { typeof( object ) } );
fieldExpression = Expression.Convert( fieldExpression, typeof( object ) );
fieldExpression = Expression.Call( typeConverterExpression, method, fieldExpression );
- var areEqualExpression = Expression.Equal( currentRecordObject, Expression.Constant( null ) );
+ var areEqualExpression = Expression.Equal( recordParameter, Expression.Constant( null ) );
fieldExpression = Expression.Condition( areEqualExpression, Expression.Constant( string.Empty ), fieldExpression );
- var writeFieldMethodCall = Expression.Call( writerParameter, "WriteField", new[] { typeof( string ) }, fieldExpression );
- expressions.Add( writeFieldMethodCall );
+ var body = Expression.Call( writerParameter, "WriteField", new[] { typeof( string ) }, fieldExpression );
+ var actionType = typeof( Action<,> ).MakeGenericType( typeof( CsvWriter ), type );
+ delegates.Add( Expression.Lambda( actionType, body, writerParameter, recordParameter ).Compile() );
}
- var body = Expression.Block( expressions );
- func = Expression.Lambda<Action<CsvWriter, T>>( body, writerParameter, recordParameter ).Compile();
-
- typeActions[type] = func;
+ typeActions[type] = Delegate.Combine( delegates.ToArray() );
}
+ return typeActions[type];
+ }
+#elif !NET_2_0
+ /// <summary>
+ /// Gets the action delegate used to write the custom
+ /// class object to the writer.
+ /// </summary>
+ /// <typeparam name="T">The type of the custom class being written.</typeparam>
+ /// <returns>The action delegate.</returns>
+ protected virtual Action<CsvWriter, T> GetWriteRecordAction<T>() where T : class
+ {
+ var type = typeof( T );
+ CreateWriteRecordAction( type, ( body, writerParameter, recordParameter ) => Expression.Lambda<Action<CsvWriter, T>>( body, writerParameter, recordParameter ).Compile() );
+
return (Action<CsvWriter, T>)typeActions[type];
}
+
+ protected virtual Action<CsvWriter, object> GetWriteRecordAction( Type type )
+ {
+ CreateWriteRecordAction( type, ( body, writerParameter, recordParameter ) => Expression.Lambda<Action<CsvWriter, object>>( body, writerParameter, recordParameter ).Compile() );
+
+ return (Action<CsvWriter, object>)typeActions[type];
+ }
+
+ protected virtual void CreateWriteRecordAction( Type type, Func<Expression, ParameterExpression, ParameterExpression, Delegate> expressionCompiler )
+ {
+ if( typeActions.ContainsKey( type ) )
+ {
+ return;
+ }
+
+ var writerParameter = Expression.Parameter( typeof( CsvWriter ), "writer" );
+ var recordParameter = Expression.Parameter( type, "record" );
+
+ if( configuration.Properties.Count == 0 )
+ {
+ configuration.AttributeMapping( type );
+ }
+
+ // Get a list of all the properties so they will
+ // be sorted properly.
+ var properties = new CsvPropertyMapCollection();
+ properties.AddRange( configuration.Properties );
+ foreach( var reference in configuration.References )
+ {
+ properties.AddRange( reference.ReferenceProperties );
+ }
+
+ // A list of expressions that will go inside
+ // the lambda code block.
+ var expressions = new List<Expression>();
+
+ foreach( var propertyMap in properties )
+ {
+ if( propertyMap.IgnoreValue )
+ {
+ // Skip ignored properties.
+ continue;
+ }
+
+ if( propertyMap.TypeConverterValue == null || !propertyMap.TypeConverterValue.CanConvertTo( typeof( string ) ) )
+ {
+ // Skip if the type isn't convertible.
+ continue;
+ }
+
+ // So we don't have to access a modified closure.
+ var propertyMapCopy = propertyMap;
+
+ // Get the reference this property is a part of.
+ var reference = ( from r in configuration.References
+ from p in r.ReferenceProperties
+ where p == propertyMapCopy
+ select r ).SingleOrDefault();
+
+ // Get the current object. Either the param passed in
+ // or a reference property object.
+ var currentRecordObject =
+ reference == null
+ ? (Expression)recordParameter
+ : Expression.Property( recordParameter, reference.Property );
+
+ Expression fieldExpression = Expression.Property( currentRecordObject, propertyMap.PropertyValue );
+ var typeConverterExpression = Expression.Constant( propertyMap.TypeConverterValue );
+ var convertMethod = Configuration.UseInvariantCulture ? "ConvertToInvariantString" : "ConvertToString";
+ var method = propertyMap.TypeConverterValue.GetType().GetMethod( convertMethod, new[] { typeof( object ) } );
+ fieldExpression = Expression.Convert( fieldExpression, typeof( object ) );
+ fieldExpression = Expression.Call( typeConverterExpression, method, fieldExpression );
+
+ var areEqualExpression = Expression.Equal( currentRecordObject, Expression.Constant( null ) );
+ fieldExpression = Expression.Condition( areEqualExpression, Expression.Constant( string.Empty ), fieldExpression );
+
+ var writeFieldMethodCall = Expression.Call( writerParameter, "WriteField", new[] { typeof( string ) }, fieldExpression );
+ expressions.Add( writeFieldMethodCall );
+ }
+
+ var body = Expression.Block( expressions );
+ var func = expressionCompiler( body, writerParameter, recordParameter );
+
+ typeActions[type] = func;
+ }
#endif
}
}
View
26 src/CsvHelper/ICsvReader.cs
@@ -219,6 +219,13 @@ public interface ICsvReader : IDisposable
T GetRecord<T>() where T : class;
/// <summary>
+ /// Gets the record.
+ /// </summary>
+ /// <param name="type">The <see cref="Type"/> of the record.</param>
+ /// <returns>The record.</returns>
+ object GetRecord( Type type );
+
+ /// <summary>
/// Gets all the records in the CSV file and
/// converts each to <see cref="Type"/> T. The Read method
/// should not be used when using this.
@@ -228,13 +235,32 @@ public interface ICsvReader : IDisposable
IEnumerable<T> GetRecords<T>() where T : class;
/// <summary>
+ /// Gets all the records in the CSV file and
+ /// converts each to <see cref="Type"/> T. The Read method
+ /// should not be used when using this.
+ /// </summary>
+ /// <param name="type">The <see cref="Type"/> of the record.</param>
+ /// <returns>An <see cref="IList{Object}" /> of records.</returns>
+ IEnumerable<object> GetRecords( Type type );
+
+ /// <summary>
/// Invalidates the record cache for the given type. After <see cref="GetRecord{T}"/> is called the
/// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
/// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
/// changes, <see cref="InvalidateRecordCache{T}"/> needs to be called to updated the
/// record cache.
/// </summary>
void InvalidateRecordCache<T>() where T : class;
+
+ /// <summary>
+ /// Invalidates the record cache for the given type. After <see cref="ICsvReader.GetRecord{T}"/> is called the
+ /// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
+ /// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
+ /// changes, <see cref="ICsvReader.InvalidateRecordCache{T}"/> needs to be called to updated the
+ /// record cache.
+ /// </summary>
+ /// <param name="type">The type to invalidate.</param>
+ void InvalidateRecordCache( Type type );
#endif
}
}
View
25 src/CsvHelper/ICsvWriter.cs
@@ -53,20 +53,45 @@ public interface ICsvWriter : IDisposable
void WriteRecord<T>( T record ) where T : class;
/// <summary>
+ /// Writes the record to the CSV file.
+ /// </summary>
+ /// <param name="type">The type of the record.</param>
+ /// <param name="record">The record to write.</param>
+ void WriteRecord( Type type, object record );
+
+ /// <summary>
/// Writes the list of records to the CSV file.
/// </summary>
/// <typeparam name="T">The type of the record.</typeparam>
/// <param name="records">The list of records to write.</param>
void WriteRecords<T>( IEnumerable<T> records ) where T : class;
/// <summary>
+ /// Writes the list of records to the CSV file.
+ /// </summary>
+ /// <param name="type">The type of the record.</param>
+ /// <param name="records">The list of records to write.</param>
+ void WriteRecords( Type type, IEnumerable<object> records );
+
+ /// <summary>
/// Invalidates the record cache for the given type. After <see cref="WriteRecord{T}"/> is called the
/// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
/// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
/// changes, <see cref="InvalidateRecordCache{T}"/> needs to be called to updated the
/// record cache.
/// </summary>
+ /// <typeparam name="T">The record type.</typeparam>
void InvalidateRecordCache<T>() where T : class;
+
+ /// <summary>
+ /// Invalidates the record cache for the given type. After <see cref="WriteRecord{T}"/> is called the
+ /// first time, code is dynamically generated based on the <see cref="CsvPropertyMapCollection"/>,
+ /// compiled, and stored for the given type T. If the <see cref="CsvPropertyMapCollection"/>
+ /// changes, <see cref="InvalidateRecordCache"/> needs to be called to updated the
+ /// record cache.
+ /// </summary>
+ /// <param name="type">The record type.</param>
+ void InvalidateRecordCache( Type type );
#endif
}
}
View
10 src/CsvHelper35.Tests/CsvHelper35.Tests.csproj
@@ -12,6 +12,8 @@
<AssemblyName>CsvHelper35.Tests</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
+ <SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
+ <RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -37,8 +39,12 @@
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
+ <Reference Include="xunit">
+ <HintPath>..\..\lib\xunit.1.9.1\lib\net20\xunit.dll</HintPath>
+ </Reference>
</ItemGroup>
<ItemGroup>
+ <Compile Include="CsvWriterTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@@ -47,7 +53,11 @@
<Name>CsvHelper35</Name>
</ProjectReference>
</ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
View
32 src/CsvHelper35.Tests/CsvWriterTests.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using CsvHelper;
+using Xunit;
+
+namespace CsvHelper35.Tests
+{
+ public class CsvWriterTests
+ {
+ [Fact]
+ public void WriteRecordNonGenericTest()
+ {
+ using( var stream = new MemoryStream() )
+ using( var writer = new StreamWriter( stream ) )
+ using( var csv = new CsvWriter( writer ) )
+ {
+ csv.WriteRecord( typeof( Test ), new Test { Id = 1, Name = "one" } );
+ csv.WriteRecord( typeof( Test ), new Test { Id = 2, Name = "two" } );
+ }
+ }
+
+ private class Test
+ {
+ public int Id { get; set; }
+
+ public string Name { get; set; }
+ }
+ }
+}
View
4 src/CsvHelper35.Tests/packages.config
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="xunit" version="1.9.1" targetFramework="net35" />
+</packages>

0 comments on commit 8335cb3

Please sign in to comment.