4646 ▀▀ */
4747
4848using System ;
49+ using System . Collections ;
4950using System . Collections . Generic ;
5051using System . Diagnostics ;
5152using System . Diagnostics . CodeAnalysis ;
@@ -66,17 +67,33 @@ public static class Extensions
6667
6768 /// <summary>
6869 /// Adds the specified key to the specified dictionary with the specified value, but only if the specified key is not
69- /// already present in the dictionary.
70+ /// already present in the dictionary. If it is present, a list is created and the new value is added to the list,
71+ /// along with all subsequent values.
7072 /// </summary>
7173 /// <param name="dictionary">The dictionary to which they specified key and value are to be added.</param>
7274 /// <param name="key">The key to add to the dictionary.</param>
7375 /// <param name="value">The value corresponding to the specified key.</param>
74- internal static void ExclusiveAdd ( this Dictionary < string , string > dictionary , string key , string value )
76+ internal static void ExclusiveAdd ( this Dictionary < string , object > dictionary , string key , object value )
7577 {
7678 if ( ! dictionary . ContainsKey ( key ) )
7779 {
7880 dictionary . Add ( key , value ) ;
7981 }
82+ else
83+ {
84+ var type = dictionary [ key ] . GetType ( ) ;
85+
86+ if ( dictionary [ key ] . GetType ( ) == typeof ( List < object > ) )
87+ {
88+ ( ( List < object > ) dictionary [ key ] ) . Add ( value ) ;
89+ }
90+ else
91+ {
92+ object existingValue = dictionary [ key ] ;
93+
94+ dictionary [ key ] = new List < object > ( new object [ ] { existingValue , value } ) ;
95+ }
96+ }
8097 }
8198
8299 #endregion Internal Methods
@@ -166,7 +183,7 @@ public class Arguments
166183 /// <param name="operandList">
167184 /// The list containing the operands specified in the command line arguments with which the application was started.
168185 /// </param>
169- private Arguments ( Dictionary < string , string > argumentDictionary , List < string > operandList )
186+ private Arguments ( Dictionary < string , object > argumentDictionary , List < string > operandList )
170187 {
171188 ArgumentDictionary = argumentDictionary ;
172189 OperandList = operandList ;
@@ -180,7 +197,7 @@ private Arguments(Dictionary<string, string> argumentDictionary, List<string> op
180197 /// Gets a dictionary containing the arguments and values specified in the command line arguments with which the
181198 /// application was started.
182199 /// </summary>
183- public Dictionary < string , string > ArgumentDictionary { get ; private set ; }
200+ public Dictionary < string , object > ArgumentDictionary { get ; private set ; }
184201
185202 /// <summary>
186203 /// Gets a list containing the operands specified in the command line arguments with which the application was started.
@@ -196,7 +213,7 @@ private Arguments(Dictionary<string, string> argumentDictionary, List<string> op
196213 /// </summary>
197214 /// <param name="index">The key for which the value is to be retrieved.</param>
198215 /// <returns>The argument value corresponding to the specified key.</returns>
199- public string this [ string index ]
216+ public object this [ string index ]
200217 {
201218 get
202219 {
@@ -221,7 +238,7 @@ public string this[string index]
221238 {
222239 commandLineString = commandLineString == default ( string ) || commandLineString == string . Empty ? Environment . CommandLine : commandLineString ;
223240
224- Dictionary < string , string > argumentDictionary ;
241+ Dictionary < string , object > argumentDictionary ;
225242 List < string > operandList ;
226243
227244 // use the strict operand regular expression to test for/extract the two halves of the string, if the operator is used.
@@ -287,7 +304,7 @@ public string this[string index]
287304 /// <param name="argumentDictionary">
288305 /// The dictionary containing the argument-value pairs with which the destination properties should be populated
289306 /// </param>
290- public static void Populate ( Dictionary < string , string > argumentDictionary )
307+ public static void Populate ( Dictionary < string , object > argumentDictionary )
291308 {
292309 Populate ( new StackFrame ( 1 ) . GetMethod ( ) . DeclaringType , new Arguments ( argumentDictionary , new List < string > ( ) ) ) ;
293310 }
@@ -319,31 +336,73 @@ public static void Populate(Type type, Arguments arguments)
319336 Type propertyType = property . PropertyType ;
320337
321338 // retrieve the value from the argument dictionary
322- string value = arguments . ArgumentDictionary [ propertyName ] ;
339+ object value = arguments . ArgumentDictionary [ propertyName ] ;
323340
324341 object convertedValue ;
325342
326343 // if the type of the property is bool and the argument value is empty set the property value to true,
327344 // indicating the argument is present
328- if ( propertyType == typeof ( bool ) && value == string . Empty )
345+ if ( propertyType == typeof ( bool ) && value . ToString ( ) == string . Empty )
329346 {
330347 convertedValue = true ;
331348 }
332- else
349+ else if ( propertyType . IsArray || ( propertyType . IsGenericType && propertyType . GetGenericTypeDefinition ( ) == typeof ( List < > ) ) )
333350 {
334- // try to cast the argument value string to the destination type
335- try
351+ // if the property is an array or list, convert the value to an array or list of the matching type. start
352+ // by converting atomic values to a list containing a single value, just to simplify processing.
353+ if ( value . GetType ( ) . IsGenericType && value . GetType ( ) . GetGenericTypeDefinition ( ) == typeof ( List < > ) )
354+ {
355+ convertedValue = value ;
356+ }
357+ else
358+ {
359+ convertedValue = new List < object > ( new object [ ] { value } ) ;
360+ }
361+
362+ // next, create a list with the same type as the target property
363+ Type valueType ;
364+
365+ if ( propertyType . IsArray )
366+ {
367+ valueType = propertyType . GetElementType ( ) ;
368+ }
369+ else
370+ {
371+ valueType = propertyType . GetGenericArguments ( ) [ 0 ] ;
372+ }
373+
374+ // create a list to store converted values
375+ Type valueListType = typeof ( List < > ) . MakeGenericType ( valueType ) ;
376+ var valueList = ( IList ) Activator . CreateInstance ( valueListType ) ;
377+
378+ // populate the list
379+ foreach ( object v in ( List < object > ) convertedValue )
336380 {
337- convertedValue = Convert . ChangeType ( value , propertyType ) ;
381+ valueList . Add ( ChangeType ( v , propertyName , valueType ) ) ;
338382 }
339- catch ( Exception ex )
383+
384+ // if the target property is an array, create one and populate it from the list this is surprisingly
385+ // difficult here because we created the source list with the Activator and ToArray() won't work easily.
386+ if ( propertyType . IsArray )
340387 {
341- // if the cast fails, throw an exception
342- string message = $ "Specified value '{ value } ' for argument '{ propertyName } ' (type: { property . PropertyType . Name } ). ";
343- message += "See inner exception for details." ;
388+ var valueArray = Array . CreateInstance ( propertyType . GetElementType ( ) , valueList . Count ) ;
389+
390+ for ( int i = 0 ; i < valueArray . Length ; i ++ )
391+ {
392+ valueArray . SetValue ( valueList [ i ] , i ) ;
393+ }
344394
345- throw new ArgumentException ( message , ex ) ;
395+ convertedValue = valueArray ;
346396 }
397+ else
398+ {
399+ convertedValue = valueList ;
400+ }
401+ }
402+ else
403+ {
404+ // if the target property Type is an atomic (non-array or list) Type, convert the value and populate it.
405+ convertedValue = ChangeType ( value , propertyName , propertyType ) ;
347406 }
348407
349408 // set the target properties' value to the converted value from the argument string
@@ -372,6 +431,29 @@ public static void Populate(Type type, Arguments arguments)
372431
373432 #region Private Methods
374433
434+ /// <summary>
435+ /// Converts the specified value for the specified argument to the specified Type.
436+ /// </summary>
437+ /// <param name="value">The value to convert.</param>
438+ /// <param name="argument">The argument for which the value is being converted.</param>
439+ /// <param name="toType">The Type to which the value is being converted.</param>
440+ /// <returns>The converted value.</returns>
441+ private static object ChangeType ( object value , string argument , Type toType )
442+ {
443+ try
444+ {
445+ return Convert . ChangeType ( value , toType ) ;
446+ }
447+ catch ( Exception ex )
448+ {
449+ // if the cast fails, throw an exception
450+ string message = $ "Specified value '{ value } ' for argument '{ argument } ' (expected type: { toType } ). ";
451+ message += "See inner exception for details." ;
452+
453+ throw new ArgumentException ( message , ex ) ;
454+ }
455+ }
456+
375457 /// <summary>
376458 /// Populates and returns a dictionary containing the values specified in the command line arguments with which the
377459 /// application was started, keyed by argument name.
@@ -381,9 +463,9 @@ public static void Populate(Type type, Arguments arguments)
381463 /// The dictionary containing the arguments and values specified in the command line arguments with which the
382464 /// application was started.
383465 /// </returns>
384- private static Dictionary < string , string > GetArgumentDictionary ( string commandLineString )
466+ private static Dictionary < string , object > GetArgumentDictionary ( string commandLineString )
385467 {
386- Dictionary < string , string > argumentDictionary = new Dictionary < string , string > ( ) ;
468+ Dictionary < string , object > argumentDictionary = new Dictionary < string , object > ( ) ;
387469
388470 foreach ( Match match in Regex . Matches ( commandLineString , ArgumentRegEx ) )
389471 {
0 commit comments