From a11833a66377310d450ed59d9faeb0a0dd623fab Mon Sep 17 00:00:00 2001 From: Josh Close Date: Thu, 21 Sep 2017 22:11:19 -0500 Subject: [PATCH] Changed DynamicInvoke to Invoke in the writer for better performance. --- src/CsvHelper.Performance/Program.cs | 6 +- src/CsvHelper/CsvHelper.csproj | 1 + src/CsvHelper/CsvWriter.cs | 88 +++++++++++++--------------- 3 files changed, 44 insertions(+), 51 deletions(-) diff --git a/src/CsvHelper.Performance/Program.cs b/src/CsvHelper.Performance/Program.cs index 21b5bc85f..809f0522b 100644 --- a/src/CsvHelper.Performance/Program.cs +++ b/src/CsvHelper.Performance/Program.cs @@ -17,10 +17,8 @@ static void Main( string[] args ) //ReadGetField(); //ReadGetRecords(); - //WriteField( 50, 1000000); - //WriteRecords( 1000000 ); - - Console.ReadKey(); + WriteField( 50, 1000000); + WriteRecords( 1000000 ); } static string GetFilePath() diff --git a/src/CsvHelper/CsvHelper.csproj b/src/CsvHelper/CsvHelper.csproj index eaa63338e..fecebd63a 100644 --- a/src/CsvHelper/CsvHelper.csproj +++ b/src/CsvHelper/CsvHelper.csproj @@ -12,6 +12,7 @@ 3.0.0.0 3.0.0.0 netstandard2.0 + 7.1 true CsvHelper diff --git a/src/CsvHelper/CsvWriter.cs b/src/CsvHelper/CsvWriter.cs index 36f8b689d..d84c26b80 100644 --- a/src/CsvHelper/CsvWriter.cs +++ b/src/CsvHelper/CsvWriter.cs @@ -395,19 +395,18 @@ public virtual void WriteDynamicHeader( IDynamicMetaObjectProvider record ) /// The record to write. public virtual void WriteRecord( T record ) { - var dynamicRecord = record as IDynamicMetaObjectProvider; - if( dynamicRecord != null ) + if( record is IDynamicMetaObjectProvider dynamicRecord ) { if( context.WriterConfiguration.HasHeaderRecord && !context.HasHeaderBeenWritten ) { WriteDynamicHeader( dynamicRecord ); - NextRecord(); + NextRecord(); } } try { - GetWriteRecordAction( record ).DynamicInvoke( record ); + GetWriteRecordAction( record ).Invoke( record ); context.HasHeaderBeenWritten = true; } catch( Exception ex ) @@ -446,13 +445,12 @@ public virtual void WriteRecords( IEnumerable records ) { recordType = record.GetType(); - var dynamicObject = record as IDynamicMetaObjectProvider; - if( dynamicObject != null ) + if( record is IDynamicMetaObjectProvider dynamicObject ) { if( context.WriterConfiguration.HasHeaderRecord && !context.HasHeaderBeenWritten ) { WriteDynamicHeader( dynamicObject ); - NextRecord(); + NextRecord(); } } else @@ -463,13 +461,13 @@ public virtual void WriteRecords( IEnumerable records ) if( context.WriterConfiguration.HasHeaderRecord && !context.HasHeaderBeenWritten && !isPrimitive ) { WriteHeader( recordType ); - NextRecord(); + NextRecord(); } } - try + try { - GetWriteRecordAction( record ).DynamicInvoke( record ); + GetWriteRecordAction( record ).Invoke( record ); } catch( TargetInvocationException ex ) { @@ -605,7 +603,7 @@ protected virtual void Dispose( bool disposing ) /// The type of the custom class being written. /// /// The action delegate. - protected virtual Delegate GetWriteRecordAction( T record ) + protected virtual Action GetWriteRecordAction( T record ) { var type = typeof( T ); if( type == typeof( object ) ) @@ -613,13 +611,12 @@ protected virtual Delegate GetWriteRecordAction( T record ) type = record.GetType(); } - Delegate action; - if( !context.TypeActions.TryGetValue( type, out action ) ) + if( !context.TypeActions.TryGetValue( type, out var action ) ) { action = CreateWriteRecordAction( type, record ); } - return action; + return (Action)action; } /// @@ -628,18 +625,17 @@ protected virtual Delegate GetWriteRecordAction( T record ) /// /// The type of the custom class being written. /// The record that will be written. - protected virtual Delegate CreateWriteRecordAction( Type type, T record ) + /// The type of the record being written. + protected virtual Action CreateWriteRecordAction( Type type, T record ) { - var expandoObject = record as ExpandoObject; - if( expandoObject != null ) + if( record is ExpandoObject expandoObject ) { - return CreateActionForExpandoObject( expandoObject ); + return CreateActionForExpandoObject( expandoObject ); } - - var dynamicObject = record as IDynamicMetaObjectProvider; - if( dynamicObject != null ) + + if( record is IDynamicMetaObjectProvider dynamicObject ) { - return CreateActionForDynamic( dynamicObject ); + return CreateActionForDynamic( dynamicObject ); } if( context.WriterConfiguration.Maps[type] == null ) @@ -650,19 +646,20 @@ protected virtual Delegate CreateWriteRecordAction( Type type, T record ) if( type.GetTypeInfo().IsPrimitive ) { - return CreateActionForPrimitive( type ); + return CreateActionForPrimitive( type ); } - return CreateActionForObject( type ); + return CreateActionForObject( type ); } /// /// Creates the action for an object. /// /// The type of object to create the action for. - protected virtual Delegate CreateActionForObject( Type type ) + protected virtual Action CreateActionForObject( Type type ) { - var recordParameter = Expression.Parameter( type, "record" ); + var recordParameter = Expression.Parameter( typeof( T ), "record" ); + var recordParameterConverted = Expression.Convert( recordParameter, type ); // Get a list of all the members so they will // be sorted properly. @@ -674,16 +671,16 @@ protected virtual Delegate CreateActionForObject( Type type ) throw new WriterException( context, $"No properties are mapped for type '{type.FullName}'." ); } - var delegates = new List(); + var delegates = new List>(); foreach( var memberMap in members ) { if( memberMap.Data.WritingConvertExpression != null ) { // The user is providing the expression to do the conversion. - Expression exp = Expression.Invoke( memberMap.Data.WritingConvertExpression, recordParameter ); + Expression exp = Expression.Invoke( memberMap.Data.WritingConvertExpression, recordParameterConverted ); exp = Expression.Call( Expression.Constant( this ), nameof( WriteConvertedField ), null, exp ); - delegates.Add( Expression.Lambda( typeof( Action<> ).MakeGenericType( type ), exp, recordParameter ).Compile() ); + delegates.Add( Expression.Lambda>( exp, recordParameter ).Compile() ); continue; } @@ -717,7 +714,7 @@ protected virtual Delegate CreateActionForObject( Type type ) continue; } - fieldExpression = CreateMemberExpression( recordParameter, context.WriterConfiguration.Maps[type], memberMap ); + fieldExpression = CreateMemberExpression( recordParameterConverted, context.WriterConfiguration.Maps[type], memberMap ); var typeConverterExpression = Expression.Constant( memberMap.Data.TypeConverter ); memberMap.Data.TypeConverterOptions = TypeConverterOptions.Merge( new TypeConverterOptions(), context.WriterConfiguration.TypeConverterOptionsFactory.GetOptions( memberMap.Data.Member.MemberType() ), memberMap.Data.TypeConverterOptions ); @@ -729,15 +726,14 @@ protected virtual Delegate CreateActionForObject( Type type ) if( type.GetTypeInfo().IsClass ) { - var areEqualExpression = Expression.Equal( recordParameter, Expression.Constant( null ) ); + var areEqualExpression = Expression.Equal( recordParameterConverted, Expression.Constant( null ) ); fieldExpression = Expression.Condition( areEqualExpression, Expression.Constant( string.Empty ), fieldExpression ); } } var writeFieldMethodCall = Expression.Call( Expression.Constant( this ), nameof( WriteConvertedField ), null, fieldExpression ); - var actionType = typeof( Action<> ).MakeGenericType( type ); - delegates.Add( Expression.Lambda( actionType, writeFieldMethodCall, recordParameter ).Compile() ); + delegates.Add( Expression.Lambda>( writeFieldMethodCall, recordParameter ).Compile() ); } var action = CombineDelegates( delegates ); @@ -750,9 +746,9 @@ protected virtual Delegate CreateActionForObject( Type type ) /// Creates the action for a primitive. /// /// The type of primitive to create the action for. - protected virtual Delegate CreateActionForPrimitive( Type type ) + protected virtual Action CreateActionForPrimitive( Type type ) { - var recordParameter = Expression.Parameter( type, "record" ); + var recordParameter = Expression.Parameter( typeof( T ), "record" ); Expression fieldExpression = Expression.Convert( recordParameter, typeof( object ) ); @@ -771,8 +767,7 @@ protected virtual Delegate CreateActionForPrimitive( Type type ) fieldExpression = Expression.Call( typeConverterExpression, method, fieldExpression, Expression.Constant( this ), Expression.Constant( memberMapData ) ); fieldExpression = Expression.Call( Expression.Constant( this ), nameof( WriteConvertedField ), null, fieldExpression ); - var actionType = typeof( Action<> ).MakeGenericType( type ); - var action = Expression.Lambda( actionType, fieldExpression, recordParameter ).Compile(); + var action = Expression.Lambda>( fieldExpression, recordParameter ).Compile(); context.TypeActions[type] = action; return action; @@ -785,9 +780,9 @@ protected virtual Delegate CreateActionForPrimitive( Type type ) /// /// The ExpandoObject. /// - protected virtual Delegate CreateActionForExpandoObject( ExpandoObject obj ) + protected virtual Action CreateActionForExpandoObject( ExpandoObject obj ) { - Action action = record => + Action action = record => { var dict = (IDictionary)record; foreach( var val in dict.Values ) @@ -805,17 +800,17 @@ protected virtual Delegate CreateActionForExpandoObject( ExpandoObject obj ) /// Creates the action for a dynamic object. /// /// The dynamic object. - protected virtual Delegate CreateActionForDynamic( IDynamicMetaObjectProvider provider ) + protected virtual Action CreateActionForDynamic( IDynamicMetaObjectProvider provider ) { // http://stackoverflow.com/a/14011692/68499 var type = provider.GetType(); - var parameterExpression = Expression.Parameter( typeof( object ), "record" ); + var parameterExpression = Expression.Parameter( typeof( T ), "record" ); var metaObject = provider.GetMetaObject( parameterExpression ); var memberNames = metaObject.GetDynamicMemberNames(); - var delegates = new List(); + var delegates = new List>(); foreach( var memberName in memberNames ) { var getMemberBinder = (GetMemberBinder)Microsoft.CSharp.RuntimeBinder.Binder.GetMember( 0, memberName, type, new[] { CSharpArgumentInfo.Create( 0, null ) } ); @@ -823,7 +818,7 @@ protected virtual Delegate CreateActionForDynamic( IDynamicMetaObjectProvider pr var fieldExpression = getMemberMetaObject.Expression; fieldExpression = Expression.Call( Expression.Constant( this ), nameof( WriteField ), new[] { typeof( object ) }, fieldExpression ); fieldExpression = Expression.Block( fieldExpression, Expression.Label( CallSiteBinder.UpdateLabel ) ); - var lambda = Expression.Lambda( fieldExpression, parameterExpression ); + var lambda = Expression.Lambda>( fieldExpression, parameterExpression ); delegates.Add( lambda.Compile() ); } @@ -841,9 +836,9 @@ protected virtual Delegate CreateActionForDynamic( IDynamicMetaObjectProvider pr /// /// The delegates to combine. /// A multicast delegate combined from the given delegates. - protected virtual Delegate CombineDelegates( IEnumerable delegates ) + protected virtual Action CombineDelegates( IEnumerable> delegates ) { - return delegates.Aggregate( null, Delegate.Combine ); + return (Action)delegates.Aggregate( null, Delegate.Combine ); } /// @@ -858,8 +853,7 @@ protected virtual bool CanWrite( MemberMap memberMap ) // Ignored members. memberMap.Data.Ignore; - var property = memberMap.Data.Member as PropertyInfo; - if( property != null ) + if( memberMap.Data.Member is PropertyInfo property ) { cantWrite = cantWrite || // Properties that don't have a public getter