Skip to content

Commit 183d47b

Browse files
committed
- caching of writer action for collections
- removed duplicate type resolving - optimized Type HashCodes - reduced memory allocation during writes
1 parent 9da7834 commit 183d47b

File tree

9 files changed

+145
-45
lines changed

9 files changed

+145
-45
lines changed

src/CsvHelper/CsvWriter.cs

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public virtual void WriteRecord<T>(T record)
331331
{
332332
try
333333
{
334-
recordManager.Value.Write(record);
334+
recordManager.Value.GetWriterAction<T>(GetTypeInfoForRecord(record))(record);
335335
}
336336
catch (TargetInvocationException ex)
337337
{
@@ -362,6 +362,9 @@ public virtual void WriteRecords(IEnumerable records)
362362
NextRecord();
363363
}
364364

365+
Action<object> writerAction = null;
366+
RecordTypeInfo writerActionType = default;
367+
365368
foreach (var record in records)
366369
{
367370
if (record == null)
@@ -371,7 +374,13 @@ public virtual void WriteRecords(IEnumerable records)
371374
continue;
372375
}
373376

374-
WriteRecord(record);
377+
if (writerAction == null || writerActionType.RecordType != record.GetType())
378+
{
379+
writerActionType = GetTypeInfoForRecord(record);
380+
writerAction = recordManager.Value.GetWriterAction<object>(writerActionType);
381+
}
382+
383+
writerAction(record);
375384
NextRecord();
376385
}
377386
}
@@ -393,9 +402,18 @@ public virtual void WriteRecords<T>(IEnumerable<T> records)
393402
NextRecord();
394403
}
395404

405+
Action<T> writerAction = null;
406+
RecordTypeInfo writerActionType = default;
407+
396408
foreach (var record in records)
397409
{
398-
WriteRecord(record);
410+
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
411+
{
412+
writerActionType = GetTypeInfoForRecord(record);
413+
writerAction = recordManager.Value.GetWriterAction<T>(writerActionType);
414+
}
415+
416+
writerAction(record);
399417
NextRecord();
400418
}
401419
}
@@ -420,11 +438,20 @@ public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationTok
420438
await NextRecordAsync().ConfigureAwait(false);
421439
}
422440

441+
Action<object> writerAction = null;
442+
RecordTypeInfo writerActionType = default;
443+
423444
foreach (var record in records)
424445
{
425446
cancellationToken.ThrowIfCancellationRequested();
426447

427-
WriteRecord(record);
448+
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
449+
{
450+
writerActionType = GetTypeInfoForRecord(record);
451+
writerAction = recordManager.Value.GetWriterAction<object>(writerActionType);
452+
}
453+
454+
writerAction(record);
428455
await NextRecordAsync().ConfigureAwait(false);
429456
}
430457
}
@@ -449,11 +476,20 @@ public virtual async Task WriteRecordsAsync<T>(IEnumerable<T> records, Cancellat
449476
await NextRecordAsync().ConfigureAwait(false);
450477
}
451478

479+
Action<T> writerAction = null;
480+
RecordTypeInfo writerActionType = default;
481+
452482
foreach (var record in records)
453483
{
454484
cancellationToken.ThrowIfCancellationRequested();
455485

456-
WriteRecord(record);
486+
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
487+
{
488+
writerActionType = GetTypeInfoForRecord(record);
489+
writerAction = recordManager.Value.GetWriterAction<T>(writerActionType);
490+
}
491+
492+
writerAction(record);
457493
await NextRecordAsync().ConfigureAwait(false);
458494
}
459495
}
@@ -478,11 +514,20 @@ public virtual async Task WriteRecordsAsync<T>(IAsyncEnumerable<T> records, Canc
478514
await NextRecordAsync().ConfigureAwait(false);
479515
}
480516

517+
Action<T> writerAction = null;
518+
RecordTypeInfo writerActionType = default;
519+
481520
await foreach (var record in records.ConfigureAwait(false))
482521
{
483522
cancellationToken.ThrowIfCancellationRequested();
484523

485-
WriteRecord(record);
524+
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
525+
{
526+
writerActionType = GetTypeInfoForRecord(record);
527+
writerAction = recordManager.Value.GetWriterAction<T>(writerActionType);
528+
}
529+
530+
writerAction(record);
486531
await NextRecordAsync().ConfigureAwait(false);
487532
}
488533
}
@@ -576,15 +621,15 @@ public virtual bool CanWrite(MemberMap memberMap)
576621
/// <typeparam name="T">The type of the record.</typeparam>
577622
/// <param name="record">The record to determine the type of.</param>
578623
/// <returns>The System.Type for the record.</returns>
579-
public virtual Type GetTypeForRecord<T>(T record)
624+
public virtual RecordTypeInfo GetTypeInfoForRecord<T>(T record)
580625
{
581626
var type = typeof(T);
582627
if (type == typeof(object))
583628
{
584-
type = record.GetType();
629+
return new RecordTypeInfo(record.GetType(), true);
585630
}
586631

587-
return type;
632+
return new RecordTypeInfo(type, false);
588633
}
589634

590635
/// <summary>

src/CsvHelper/Expressions/DynamicRecordWriter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public DynamicRecordWriter(CsvWriter writer) : base(writer) { }
3030
/// that will write the given record using the current writer row.
3131
/// </summary>
3232
/// <typeparam name="T">The record type.</typeparam>
33-
/// <param name="record">The record.</param>
34-
protected override Action<T> CreateWriteDelegate<T>(T record)
33+
/// <param name="type">The type for the record.</param>
34+
protected override Action<T> CreateWriteDelegate<T>(Type type)
3535
{
3636
// http://stackoverflow.com/a/14011692/68499
3737

@@ -72,4 +72,4 @@ private object GetValue(string name, IDynamicMetaObjectProvider target)
7272
return callSite.Target(callSite, target);
7373
}
7474
}
75-
}
75+
}

src/CsvHelper/Expressions/ExpandoObjectRecordWriter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ public ExpandoObjectRecordWriter(CsvWriter writer) : base(writer) { }
2424
/// that will write the given record using the current writer row.
2525
/// </summary>
2626
/// <typeparam name="T">The record type.</typeparam>
27-
/// <param name="record">The record.</param>
28-
protected override Action<T> CreateWriteDelegate<T>(T record)
27+
/// <param name="type">The type for the record.</param>
28+
protected override Action<T> CreateWriteDelegate<T>(Type type)
2929
{
3030
Action<T> action = r =>
3131
{
@@ -46,4 +46,4 @@ protected override Action<T> CreateWriteDelegate<T>(T record)
4646
return action;
4747
}
4848
}
49-
}
49+
}

src/CsvHelper/Expressions/ObjectRecordWriter.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,9 @@ public ObjectRecordWriter(CsvWriter writer) : base(writer) { }
2727
/// that will write the given record using the current writer row.
2828
/// </summary>
2929
/// <typeparam name="T">The record type.</typeparam>
30-
/// <param name="record">The record.</param>
31-
protected override Action<T> CreateWriteDelegate<T>(T record)
30+
/// <param name="type">The type for the record.</param>
31+
protected override Action<T> CreateWriteDelegate<T>(Type type)
3232
{
33-
var type = Writer.GetTypeForRecord(record);
34-
3533
if (Writer.Context.Maps[type] == null)
3634
{
3735
Writer.Context.Maps.Add(Writer.Context.AutoMap(type));

src/CsvHelper/Expressions/PrimitiveRecordWriter.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@ public PrimitiveRecordWriter(CsvWriter writer) : base(writer) { }
2525
/// that will write the given record using the current writer row.
2626
/// </summary>
2727
/// <typeparam name="T">The record type.</typeparam>
28-
/// <param name="record">The record.</param>
29-
protected override Action<T> CreateWriteDelegate<T>(T record)
28+
/// <param name="type">The type for the record.</param>
29+
protected override Action<T> CreateWriteDelegate<T>(Type type)
3030
{
31-
var type = Writer.GetTypeForRecord(record);
32-
3331
var recordParameter = Expression.Parameter(typeof(T), "record");
3432

3533
Expression fieldExpression = Expression.Convert(recordParameter, typeof(object));

src/CsvHelper/Expressions/RecordManager.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,15 @@ public void Hydrate<T>(T record)
6767
}
6868

6969
/// <summary>
70-
/// Writes the given record to the current writer row.
70+
/// Gets Writer Action to write multiple rows faster
7171
/// </summary>
72-
/// <typeparam name="T">The type of the record.</typeparam>
73-
/// <param name="record">The record.</param>
74-
public void Write<T>(T record)
72+
/// <param name="typeInfo"></param>
73+
/// <typeparam name="T"></typeparam>
74+
/// <returns></returns>
75+
internal Action<T> GetWriterAction<T>(RecordTypeInfo typeInfo)
7576
{
76-
var recordWriter = recordWriterFactory.MakeRecordWriter(record);
77-
recordWriter.Write(record);
77+
var recordWriter = recordWriterFactory.MakeRecordWriter(typeInfo);
78+
return recordWriter.GetWriteDelegate<T>(typeInfo);
7879
}
7980
}
8081
}

src/CsvHelper/Expressions/RecordWriter.cs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void Write<T>(T record)
6666
/// </summary>
6767
/// <typeparam name="T">The record type.</typeparam>
6868
/// <param name="record">The record.</param>
69-
protected Action<T> GetWriteDelegate<T>(T record)
69+
protected internal Action<T> GetWriteDelegate<T>(T record)
7070
{
7171
var type = typeof(T);
7272
var typeKeyName = type.AssemblyQualifiedName;
@@ -86,13 +86,44 @@ protected Action<T> GetWriteDelegate<T>(T record)
8686
return (Action<T>)action;
8787
}
8888

89+
/// <summary>
90+
/// Gets the delegate to write the given record.
91+
/// If the delegate doesn't exist, one will be created and cached.
92+
/// </summary>
93+
/// <typeparam name="T">The record type.</typeparam>
94+
/// <param name="typeInfo">The type for the record.</param>
95+
protected internal Action<T> GetWriteDelegate<T>(RecordTypeInfo typeInfo)
96+
{
97+
var typeKey = typeInfo.RecordType.GetHashCode();
98+
99+
if (typeInfo.IsItemType)
100+
typeKey = HashCode.Combine(17180427, typeKey);
101+
102+
if (!typeActions.TryGetValue(typeKey, out Delegate action))
103+
{
104+
typeActions[typeKey] = action = CreateWriteDelegate<T>(typeInfo.RecordType);
105+
}
106+
107+
return (Action<T>)action;
108+
}
109+
89110
/// <summary>
90111
/// Creates a <see cref="Delegate"/> of type <see cref="Action{T}"/>
91112
/// that will write the given record using the current writer row.
92113
/// </summary>
93114
/// <typeparam name="T">The record type.</typeparam>
94115
/// <param name="record">The record.</param>
95-
protected abstract Action<T> CreateWriteDelegate<T>(T record);
116+
protected virtual Action<T> CreateWriteDelegate<T>(T record)
117+
{
118+
return CreateWriteDelegate<T>(Writer.GetTypeInfoForRecord(record).RecordType);
119+
}
120+
121+
/// <summary>
122+
/// Creates a <see cref="Delegate"/> of type <see cref="Action{T}"/>
123+
/// that will write the given record using the current writer row.
124+
/// </summary>
125+
/// <param name="typeForRecord">The type for the record.</param>
126+
protected abstract Action<T> CreateWriteDelegate<T>(Type typeForRecord);
96127

97128
/// <summary>
98129
/// Combines the delegates into a single multicast delegate.

src/CsvHelper/Expressions/RecordWriterFactory.cs

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0.
33
// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0.
44
// https://github.com/JoshClose/CsvHelper
5+
56
using System.Dynamic;
6-
using System.Reflection;
77

88
namespace CsvHelper.Expressions
99
{
@@ -34,27 +34,23 @@ public RecordWriterFactory(CsvWriter writer)
3434
/// <summary>
3535
/// Creates a new record writer for the given record.
3636
/// </summary>
37-
/// <typeparam name="T">The type of the record.</typeparam>
38-
/// <param name="record">The record.</param>
39-
public virtual RecordWriter MakeRecordWriter<T>(T record)
37+
/// <param name="typeInfo">The type of the record.</param>
38+
public virtual RecordWriter MakeRecordWriter(RecordTypeInfo typeInfo)
4039
{
41-
var type = writer.GetTypeForRecord(record);
40+
var type = typeInfo.RecordType;
4241

43-
if (record is ExpandoObject expandoObject)
42+
if (type.IsPrimitive)
4443
{
45-
return expandoObjectRecordWriter;
44+
return primitiveRecordWriter;
4645
}
47-
48-
if (record is IDynamicMetaObjectProvider dynamicObject)
46+
if (typeof(ExpandoObject).IsAssignableFrom(type))
4947
{
50-
return dynamicRecordWriter;
48+
return expandoObjectRecordWriter;
5149
}
52-
53-
if (type.GetTypeInfo().IsPrimitive)
50+
if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type))
5451
{
55-
return primitiveRecordWriter;
52+
return dynamicRecordWriter;
5653
}
57-
5854
return objectRecordWriter;
5955
}
6056
}

src/CsvHelper/RecordTypeInfo.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
3+
namespace CsvHelper
4+
{
5+
/// <summary>
6+
/// Container for type info about written records
7+
/// </summary>
8+
public struct RecordTypeInfo
9+
{
10+
/// <summary>
11+
/// .ctor
12+
/// </summary>
13+
/// <param name="recordType"></param>
14+
/// <param name="isItemType"></param>
15+
public RecordTypeInfo(Type recordType, bool isItemType)
16+
{
17+
RecordType = recordType;
18+
IsItemType = isItemType;
19+
}
20+
21+
/// <summary>
22+
/// Final type of the record
23+
/// </summary>
24+
public Type RecordType { get; }
25+
26+
/// <summary>
27+
/// Is this type from record item
28+
/// </summary>
29+
public bool IsItemType { get; }
30+
}
31+
}

0 commit comments

Comments
 (0)