Comme plusieurs d'entre vous j'ai eu l'occasion de voir des démos assez bleuffantes sur le Parallel Framework (dont vous pouvez télécharger la CTP de décembre ici), à commencer par celle lors de la pleinière du lundi matin aux techdays.
Pour ceux qui l'ignorent ce Framework permet de très simplement paralléliser notre code afin d'utiliser l'ensemble des coeurs présents sur nos machines modernes.
J'ai voulu voir ce que ça donnait sur un algo aussi simple que le tri à bulle dont voici une implémentation "classique" :
static int[] ClassicBubbleSort(int[] array)
{
int maxI = array.Length - 1;
bool continueSort = true;
while (continueSort)
{
continueSort = false;
for (int i = 0; i < maxI; ) // ouais je sais là j'abuse à complexifié l'exemple pour rien mais je trouve ça plus fun comme ça
et c'est plus optimal
if (Sort(ref array[i++], ref array[ i ]))
continueSort = true;
}
return array;
}
static bool Sort(ref int item1, ref int item2)
{
if (item1 > item2)
{
int item1Bis = item1;
item1 = item2;
item2 = item1Bis;
return true;
}
return false;
}
Avec le parallel framework, on a une méthode For qui va nous permettre de paralléliser les itérations dans la boucle. Une seule contrainte par contre : l'état à l'itération in ne doit pas dépendre de l'état de in-1.
Aussi, il faut repenser l'algo du tri à bulle.
C'est ce que j'ai fait comme ceci dans un premier temps :
static int[] ParallelBubbleSort1(int[] array)
{
int maxI = array.Length - 1;
bool continueSort = true;
while (continueSort)
{
continueSort = false;
System.Threading.Parallel.For(0, maxI, 2, i =>
{
if (Sort(ref array[ i ], ref array[i + 1]))
continueSort = true;
});
System.Threading.Parallel.For(1, maxI, 2, i =>
{
if (Sort(ref array[ i ], ref array[i + 1]))
continueSort = true;
});
}
return array;
}
Mais là, les temps de réponses sont en réalité bien moins bons qu'avec notre première implémentation. Pourquoi ?
La parallélisation du code, c'est pas gratuit et ça prend du temps. Et vu la simplicité du code qui est effectué dans la boucle (la méthode Sort), ça ne vaut pas le coup. De plus, on peut supposer (il faudra que je creuse), que l'affectation de continueSort se fait avec un mutex pour protéger l'écriture mais ce qui a pour effet de ralentir les threads qui vont se retrouver bloquer à cause d'accès concurrentiel.
Du coup, on pourrait être tenté de transformer notre code comme ceci en appliquant l'adage diviser pour mieux reigner :
static int[] ParallelBubbleSort2(int[] array)
{
if (array.Length == 0)
return array;
int maxI = array.Length;
bool continueSort = true;
int nbProc = Environment.ProcessorCount;
Action[] actions = new Action[nbProc];
Action[] actions2 = new Action[nbProc - 1];
bool[] continueSorts = new bool[nbProc];
int middleI = (maxI--) / nbProc;
int maxIndexProc = nbProc - 1;
for (int indexProc = 0; indexProc < maxIndexProc; indexProc++)
{
int first = indexProc * middleI;
int last = first + middleI;
int indexPropCopy = indexProc;
actions[indexProc] = () =>
{
continueSorts[indexPropCopy] = false;
for (int i = first; i < last; )
if (Sort(ref array[i++], ref array[ i ]))
continueSorts[indexPropCopy] = true;
};
actions2[indexProc] = () =>
{
if (Sort(ref array[last], ref array[last + 1]))
continueSorts[indexPropCopy] = true;
};
}
actions[maxIndexProc] = () =>
{
continueSorts[maxIndexProc] = false;
for (int i = maxIndexProc * middleI; i < maxI; )
if (Sort(ref array[i++], ref array[ i ]))
continueSorts[maxIndexProc] = true;
};
while (continueSort)
{
System.Threading.Parallel.Do(actions);
System.Threading.Parallel.Do(actions2);
continueSort = continueSorts.Any(continueSortTmp => continueSortTmp);
}
return array;
}
Mais là aussi, mes quelques tests montrent que ce code est moins performant que la version de base.
Dans un premier temps, on va supprimer la parallélisation sur actions2 qui, étant extrèmement restreinte nous fait plus perdre en performance qu'autre chose :
static int[] ParallelBubbleSort3(int[] array)
{
if (array.Length == 0)
return array;
int maxI = array.Length;
bool continueSort = true;
int nbProc = Environment.ProcessorCount;
Action[] actions = new Action[nbProc];
Action[] actions2 = new Action[nbProc - 1];
bool[] continueSorts = new bool[nbProc];
int middleI = (maxI--) / nbProc;
int maxIndexProc = nbProc - 1;
for (int indexProc = 0; indexProc < maxIndexProc; indexProc++)
{
int first = indexProc * middleI;
int last = first + middleI;
int indexPropCopy = indexProc;
actions[indexProc] = () =>
{
continueSorts[indexPropCopy] = false;
for (int i = first; i < last; )
if (Sort(ref array[i++], ref array[ i ]))
continueSorts[indexPropCopy] = true;
};
actions2[indexProc] = () =>
{
if (Sort(ref array[last], ref array[last + 1]))
continueSorts[indexPropCopy] = true;
};
}
actions[maxIndexProc] = () =>
{
continueSorts[maxIndexProc] = false;
for (int i = maxIndexProc * middleI; i < maxI; )
if (Sort(ref array[i++], ref array[ i ]))
continueSorts[maxIndexProc] = true;
};
while (continueSort)
{
System.Threading.Parallel.Do(actions);
foreach (Action action2 in actions2)
action2();
continueSort = continueSorts.Any(continueSortTmp => continueSortTmp);
}
return array;
}
Afin d'éviter d'aller n fois au même index dans le tableau, j'ai essayé une approche par pointeur pour optimiser mon code :
static int[] ParallelBubbleSort4(int[] array)
{
if (array.Length == 0)
return array;
int maxI = array.Length;
int nbProc = Environment.ProcessorCount;
Action[] actions = new Action[nbProc];
Action[] actions2 = new Action[nbProc - 1];
bool[] continueSorts = new bool[nbProc];
bool continueSort = true;
int middleI = (maxI--) / nbProc;
int maxIndexProc = nbProc - 1;
for (int indexProc = 0; indexProc < maxIndexProc; indexProc++)
{
int first = indexProc * middleI;
int last = first + middleI;
int indexPropCopy = indexProc;
actions[indexProc] = () =>
{
unsafe
{
fixed (bool* continueSortTmp = &continueSorts[indexPropCopy])
{
*continueSortTmp = false;
for (int i = first; i < last; )
{
fixed (int* item1 = &array[i++])
{
fixed (int* item2 = &array[ i ])
{
if (Sort(item1, item2))
*continueSortTmp = true;
}
}
}
}
}
};
actions2[indexProc] = () =>
{
unsafe
{
fixed (int* item1 = &array[last])
{
fixed (int* item2 = &array[last + 1])
{
if (Sort(ref array[last], ref array[last + 1]))
continueSorts[indexPropCopy] = true;
}
}
}
};
}
actions[maxIndexProc] = () =>
{
unsafe
{
fixed (bool* continueSortTmp = &continueSorts[maxIndexProc])
{
*continueSortTmp = false;
for (int i = maxIndexProc * middleI; i < maxI; )
fixed (int* item1 = &array[i++])
{
fixed (int* item2 = &array[ i ])
{
if (Sort(item1, item2))
*continueSortTmp = true;
}
}
}
}
};
while (continueSort)
{
System.Threading.Parallel.Do(actions);
foreach (Action action2 in actions2)
action2();
continueSort = continueSorts.Any(continueSortTmp => continueSortTmp);
}
return array;
}
unsafe static bool Sort(int* item1, int* item2)
{
int item1Value = *item1;
int item2Value = *item2;
if (item1Value > item2Value)
{
*item1 = item2Value;
*item2 = item1Value;
return true;
}
return false;
}
Ce qu'il faut noter c'est que la version parallélisable est de mieux en mieux mais reste tout de même moins performante que la version "classique" dans le cadre de mes tests. Par contre, elles monopolisent bien 100% de mes deux coeurs.
J'ai alors pensé à (encore) un autre algo.
Mon algo classique n'est pas optimal loin de là. Je l'ai donc repensé comme ceci :
static int[] ClassicBubbleSort2(int[] array)
{
int maxI = array.Length - 1;
for (int i = 0; i < maxI; i ++)
{
if (!Sort(ref array[ i ], ref array[i + 1]))
continue;
for (int j = i; j > 0 && Sort(ref array[j - 1], ref array[j]); j--) ;
}
return array;
}
Cet algo n'est pas parallélisable car in dépend de in-1. La seule possibilité éventuelle, mais qui n'utiliserait pas le Parallel Framework, serait de créer un thread pour la deuxième boucle for pour continuer la première en même temps mais, dans le cas où on repasserait dans la deuxième boucle for, attendre que le thread soit terminé. Bref, bof.
Conclusion : la parallélisation du code est devenue très facile et peux s'avérer très efficace cependant, paralléliser un algo n'est pas toujours la meilleure solution et il vaut mieux parfois tout simplement optimiser notre algo écrit sans prendre en compte la problématique multi-coeur.
Il est important de noter également que les performances sont très relatives au nombre de coeurs et, dans le cas du tri à bulle, à la taille du tableau à trier.
Mon post aurait pu s'arrêter là mais j'ai persévéré et je me suis dit que j'allais utiliser ce nouvel algo et, parallélement, j'allais prémaché le travail :
static int[] ParallelBubbleSort5(int[] array)
{
int maxI = array.Length;
int nbProc = Environment.ProcessorCount;
Action[] actions = new Action[nbProc];
Action[] actions2 = new Action[nbProc - 1];
int middleI = (maxI--) / nbProc;
int maxIndexProc = nbProc - 1;
for (int indexProc = 0; indexProc < maxIndexProc; indexProc++)
{
int first = indexProc * middleI;
int last = first + middleI - 1;
int indexPropCopy = indexProc;
actions[indexProc] = () =>
{
unsafe
{
for (int i = first; i < last; )
{
fixed (int* item1 = &array[i++])
{
fixed (int* item2 = &array[ i ])
{
if (!Sort(item1, item2))
continue;
}
}
for (int j = i - 1; j > first && Sort(ref array[j - 1], ref array[j]); j--) ;
}
}
};
}
actions[maxIndexProc] = () =>
{
unsafe
{
int first = maxIndexProc * middleI;
for (int i = first; i < maxI; )
{
fixed (int* item1 = &array[i++])
{
fixed (int* item2 = &array[ i ])
{
if (!Sort(item1, item2))
continue;
}
}
for (int j = i - 1; j > first && Sort(ref array[j - 1], ref array[j]); j--) ;
}
}
};
System.Threading.Parallel.Do(actions);
while (nbProc != 1)
{
maxIndexProc = nbProc - 1;
middleI = maxI / nbProc;
Parallel.Do(
() =>
{
Parallel.For(1, maxIndexProc - 1, (indexProc) =>
{
unsafe
{
int first = middleI * indexProc;
int last = first + middleI;
for (int i = first; i <= last; )
{
fixed (int* item1 = &array[i++])
{
fixed (int* item2 = &array[ i ])
{
if (!Sort(item1, item2))
continue;
}
}
for (int j = i - 1; j > 0 && Sort(ref array[j - 1], ref array[j]); j--) ;
}
}
});
},
() =>
{
unsafe
{
for (int i = middleI * maxIndexProc; i < maxI; )
{
fixed (int* item1 = &array[i++])
{
fixed (int* item2 = &array[ i ])
{
if (!Sort(item1, item2))
continue;
}
}
for (int j = i - 1; j > 0 && Sort(ref array[j - 1], ref array[j]); j--) ;
}
}
});
nbProc /= 2;
}
return array;
}
Voici les benchs que j'ai obtenu avec mon portable Dual Core :
Temps en ms pour un tableau de 1 000 éléments (Random) :
ClassicBubbleSort 8
ClassicBubbleSort2 2
ParallelBubbleSort1 31
ParallelBubbleSort2 41
ParallelBubbleSort3 37
ParallelBubbleSort4 22
ParallelBubbleSort5 5
Temps en ms pour un tableau de 100 000 éléments (Random) :
ClassicBubbleSort 92 444
ClassicBubbleSort2 17 675
ParallelBubbleSort1 202 874
ParallelBubbleSort2 141 495
ParallelBubbleSort3 140 160
ParallelBubbleSort4 96 079
ParallelBubbleSort5 17 418
Temps en ms pour un tableau de 500 000 éléments (Random) :
ClassicBubbleSort2 454 147
ParallelBubbleSort5 416 394
Temps en ms pour un tableau de 1 000 000 éléments (Random) :
ClassicBubbleSort2 1 801 093
ParallelBubbleSort5 1 759 334
Suite à la nouvelle remarque de Jean-Baptiste, mon code devient finalement ceci :
namespace ConsoleApplication81
{
class Program
{
static void Main(string[] args)
{
using (var context = new DataClasses1DataContext())
{
var dt = LinqTableToDataTableHelper<Category>.GetDataTableFromLINQTable(context.Categories);
}
}
}
public static class LinqTableToDataTableHelper<LTT>
where LTT : class
{
private static Func<Table<LTT>, DataTable> _generatedMethodDelegate;
public static DataTable GetDataTableFromLINQTable(Table<LTT> linqTable)
{
if (_generatedMethodDelegate == null)
_generatedMethodDelegate = GenerateTableConverter().CreateDelegate(typeof(Func<Table<LTT>, DataTable>)) as Func<Table<LTT>, DataTable>;
return _generatedMethodDelegate(linqTable);
}
public static DynamicMethod GenerateTableConverter()
{
var dynamicMethod = new DynamicMethod("Convert", typeof(DataTable), new Type[] { typeof(Table<LTT>) });
var convertIlGenerator = dynamicMethod.GetILGenerator();
convertIlGenerator.DeclareLocal(typeof(DataTable));
convertIlGenerator.DeclareLocal(typeof(IEnumerator<LTT>));
convertIlGenerator.DeclareLocal(typeof(LTT));
convertIlGenerator.DeclareLocal(typeof(DataRow));
var propertyValue = convertIlGenerator.DeclareLocal(typeof(object));
convertIlGenerator.Emit(OpCodes.Ldarg_0);
var convertIlGeneratorArgOkLabel = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Brtrue_S, convertIlGeneratorArgOkLabel);
convertIlGenerator.Emit(OpCodes.Newobj, typeof(ArgumentException).GetConstructor(new Type[0]));
convertIlGenerator.Emit(OpCodes.Throw);
convertIlGenerator.MarkLabel(convertIlGeneratorArgOkLabel);
convertIlGenerator.Emit(OpCodes.Newobj, typeof(DataTable).GetConstructor(new Type[0]));
convertIlGenerator.Emit(OpCodes.Stloc_0);
var properties = typeof(LTT).GetProperties().Where(p => p.GetAttribute<ColumnAttribute>() != null);
foreach (var pi in properties)
{
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("get_Columns"));
convertIlGenerator.Emit(OpCodes.Ldstr, pi.Name);
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
convertIlGenerator.Emit(OpCodes.Ldtoken, pi.PropertyType.GetGenericArguments()[0]);
else
convertIlGenerator.Emit(OpCodes.Ldtoken, pi.PropertyType);
convertIlGenerator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataColumnCollection).GetMethod("Add", new Type[] { typeof(string), typeof(Type) }));
convertIlGenerator.Emit(OpCodes.Pop);
}
var convertIlGeneratorEntitiesLoopLabel = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Ldarg_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerable<LTT>).GetMethod("GetEnumerator"));
convertIlGenerator.Emit(OpCodes.Stloc_1);
convertIlGenerator.MarkLabel(convertIlGeneratorEntitiesLoopLabel);
convertIlGenerator.Emit(OpCodes.Ldloc_1);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerator).GetMethod("MoveNext"));
var convertIlGeneratorEntitiesEndLoopLable = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Brfalse, convertIlGeneratorEntitiesEndLoopLable);
convertIlGenerator.Emit(OpCodes.Ldloc_1);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerator<LTT>).GetMethod("get_Current"));
convertIlGenerator.Emit(OpCodes.Stloc_2);
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("get_Rows"));
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("NewRow"));
convertIlGenerator.Emit(OpCodes.Stloc_3);
foreach (var pi in properties)
{
convertIlGenerator.Emit(OpCodes.Ldloc_2);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(LTT).GetMethod("get_" + pi.Name, new Type[0]));
if (pi.PropertyType.IsValueType)
convertIlGenerator.Emit(OpCodes.Box, pi.PropertyType);
convertIlGenerator.Emit(OpCodes.Stloc, propertyValue);
convertIlGenerator.Emit(OpCodes.Ldloc, propertyValue);
var convertIlGeneratorNextPropertyLabel = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Brfalse_S, convertIlGeneratorNextPropertyLabel);
convertIlGenerator.Emit(OpCodes.Ldloc_3);
convertIlGenerator.Emit(OpCodes.Ldstr, pi.Name);
convertIlGenerator.Emit(OpCodes.Ldloc, propertyValue);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataRow).GetMethod("set_Item", new Type[] { typeof(string), typeof(object) }));
convertIlGenerator.MarkLabel(convertIlGeneratorNextPropertyLabel);
}
convertIlGenerator.Emit(OpCodes.Ldloc_3);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataRowCollection).GetMethod("Add", new Type[] { typeof(DataRow) }));
convertIlGenerator.Emit(OpCodes.Br, convertIlGeneratorEntitiesLoopLabel);
convertIlGenerator.MarkLabel(convertIlGeneratorEntitiesEndLoopLable);
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Ret);
return dynamicMethod;
}
}
}
namespace System.Reflection
{
public static class PropertyInfoExtension
{
public static T GetAttribute<T>(this PropertyInfo pi) where T : Attribute
{
object[] attributes = pi.GetCustomAttributes(typeof(T), true);
if (attributes.Length == 0)
return null;
return attributes[0] as T;
}
}
}
C'est pas parce que je suis au MVP Summit que ça va m'empêcher de coder la nuit. 
J'avais précédemment blogué sur la génération de code à la volée qui permettait de convertir une Table LINQ To SQL en DataTable.
Suite à
- la remarque de Jean-Baptiste
- le fait que ça m'embêtait de garder un appel par réflection et que je voulais utiliser un expression tree à la place
- le fait que je me suis dit que plutôt que de mettre les méthodes génériques et d'avoir un dictionnaire avec le type en clé, je ferais mieux de mettre la généricité au niveau de la classe
J'ai repris mon code pour ceci :
namespace ConsoleApplication81
{
class Program
{
static void Main(string[] args)
{
using (var context = new DataClasses1DataContext())
{
var dt = LinqTableToDataTableHelper<Category>.GetDataTableFromLINQTable(context.Categories);
}
}
}
public static class LinqTableToDataTableHelper<LTT>
where LTT : class
{
private static DynamicMethod _generatedType;
private static Delegate _lambdaExprCompiled;
public static DataTable GetDataTableFromLINQTable(Table<LTT> linqTable)
{
if (_lambdaExprCompiled == null)
{
var parameterExpression = Expression.Parameter(typeof(Table<LTT>), "linqTable");
var lambdaExpression = Expression.Lambda(
Expression.Call(GetGeneratedMethod(), new ParameterExpression[] { parameterExpression }),
parameterExpression);
_lambdaExprCompiled = lambdaExpression.Compile();
}
return _lambdaExprCompiled.DynamicInvoke(linqTable) as DataTable;
}
private static DynamicMethod GetGeneratedMethod()
{
if (_generatedType != null)
return _generatedType;
return (_generatedType = GenerateTableConverter());
}
public static DynamicMethod GenerateTableConverter()
{
var dynamicMethod = new DynamicMethod("Convert", typeof(DataTable), new Type[] { typeof(Table<LTT>) });
var convertIlGenerator = dynamicMethod.GetILGenerator();
convertIlGenerator.DeclareLocal(typeof(DataTable));
convertIlGenerator.DeclareLocal(typeof(IEnumerator<LTT>));
convertIlGenerator.DeclareLocal(typeof(LTT));
convertIlGenerator.DeclareLocal(typeof(DataRow));
var propertyValue = convertIlGenerator.DeclareLocal(typeof(object));
convertIlGenerator.Emit(OpCodes.Ldarg_0);
var convertIlGeneratorArgOkLabel = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Brtrue_S, convertIlGeneratorArgOkLabel);
convertIlGenerator.Emit(OpCodes.Newobj, typeof(ArgumentException).GetConstructor(new Type[0]));
convertIlGenerator.Emit(OpCodes.Throw);
convertIlGenerator.MarkLabel(convertIlGeneratorArgOkLabel);
convertIlGenerator.Emit(OpCodes.Newobj, typeof(DataTable).GetConstructor(new Type[0]));
convertIlGenerator.Emit(OpCodes.Stloc_0);
var properties = typeof(LTT).GetProperties().Where(p => p.GetAttribute<ColumnAttribute>() != null);
foreach (var pi in properties)
{
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("get_Columns"));
convertIlGenerator.Emit(OpCodes.Ldstr, pi.Name);
if (pi.PropertyType.IsGenericType && pi.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
convertIlGenerator.Emit(OpCodes.Ldtoken, pi.PropertyType.GetGenericArguments()[0]);
else
convertIlGenerator.Emit(OpCodes.Ldtoken, pi.PropertyType);
convertIlGenerator.Emit(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] { typeof(RuntimeTypeHandle) }));
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataColumnCollection).GetMethod("Add", new Type[] { typeof(string), typeof(Type) }));
convertIlGenerator.Emit(OpCodes.Pop);
}
var convertIlGeneratorEntitiesLoopLabel = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Ldarg_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerable<LTT>).GetMethod("GetEnumerator"));
convertIlGenerator.Emit(OpCodes.Stloc_1);
convertIlGenerator.MarkLabel(convertIlGeneratorEntitiesLoopLabel);
convertIlGenerator.Emit(OpCodes.Ldloc_1);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerator).GetMethod("MoveNext"));
var convertIlGeneratorEntitiesEndLoopLable = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Brfalse, convertIlGeneratorEntitiesEndLoopLable);
convertIlGenerator.Emit(OpCodes.Ldloc_1);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(IEnumerator<LTT>).GetMethod("get_Current"));
convertIlGenerator.Emit(OpCodes.Stloc_2);
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("get_Rows"));
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataTable).GetMethod("NewRow"));
convertIlGenerator.Emit(OpCodes.Stloc_3);
foreach (var pi in properties)
{
convertIlGenerator.Emit(OpCodes.Ldloc_2);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(LTT).GetMethod("get_" + pi.Name, new Type[0]));
if (pi.PropertyType.IsValueType)
convertIlGenerator.Emit(OpCodes.Box, pi.PropertyType);
convertIlGenerator.Emit(OpCodes.Stloc, propertyValue);
convertIlGenerator.Emit(OpCodes.Ldloc, propertyValue);
var convertIlGeneratorNextPropertyLabel = convertIlGenerator.DefineLabel();
convertIlGenerator.Emit(OpCodes.Brfalse_S, convertIlGeneratorNextPropertyLabel);
convertIlGenerator.Emit(OpCodes.Ldloc_3);
convertIlGenerator.Emit(OpCodes.Ldstr, pi.Name);
convertIlGenerator.Emit(OpCodes.Ldloc, propertyValue);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataRow).GetMethod("set_Item", new Type[] { typeof(string), typeof(object) }));
convertIlGenerator.MarkLabel(convertIlGeneratorNextPropertyLabel);
}
convertIlGenerator.Emit(OpCodes.Ldloc_3);
convertIlGenerator.Emit(OpCodes.Callvirt, typeof(DataRowCollection).GetMethod("Add", new Type[] { typeof(DataRow) }));
convertIlGenerator.Emit(OpCodes.Br, convertIlGeneratorEntitiesLoopLabel);
convertIlGenerator.MarkLabel(convertIlGeneratorEntitiesEndLoopLable);
convertIlGenerator.Emit(OpCodes.Ldloc_0);
convertIlGenerator.Emit(OpCodes.Ret);
return dynamicMethod;
}
}
}
namespace System.Reflection
{
public static class PropertyInfoExtension
{
public static T GetAttribute<T>(this PropertyInfo pi) where T : Attribute
{
object[] attributes = pi.GetCustomAttributes(typeof(T), true);
if (attributes.Length == 0)
return null;
return attributes[0] as T;
}
}
}