Skip to content

Commit 443641c

Browse files
committed
Merge pull request #76 from rkeithhill/rkeithhill/variable-dictionary-bug
For for issue #75, variables display isn't displaying anything for ge…
2 parents 6a83d00 + df0288c commit 443641c

File tree

2 files changed

+116
-47
lines changed

2 files changed

+116
-47
lines changed

src/PowerShellEditorServices/Debugging/VariableDetails.cs

+114-45
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ private static bool GetIsExpandable(object valueObject)
129129

130130
return
131131
valueObject != null &&
132-
!valueType.IsValueType &&
132+
!valueType.IsPrimitive &&
133+
!(valueObject is decimal) &&
134+
!(valueObject is UnableToRetrievePropertyMessage) &&
133135
!(valueObject is string); // Strings get treated as IEnumerables
134136
}
135137

@@ -143,13 +145,23 @@ private static string GetValueString(object value, bool isExpandable)
143145
}
144146
else if (isExpandable)
145147
{
146-
Type objType = value.GetType();
148+
Type objType = value.GetType();
147149

148-
// Get the "value" for an expandable object. This will either
149-
// be the short type name or the ToString() response if ToString()
150-
// responds with something other than the type name.
151-
if (value.ToString().Equals(objType.FullName))
150+
// Get the "value" for an expandable object.
151+
if (value is DictionaryEntry)
152152
{
153+
// For DictionaryEntry - display the key/value as the value.
154+
var entry = (DictionaryEntry)value;
155+
valueString =
156+
string.Format(
157+
"[{0}, {1}]",
158+
entry.Key,
159+
GetValueString(entry.Value, GetIsExpandable(entry.Value)));
160+
}
161+
else if (value.ToString().Equals(objType.ToString()))
162+
{
163+
// If the ToString() matches the type name, then display the type
164+
// name in PowerShell format.
153165
string shortTypeName = objType.Name;
154166

155167
// For arrays and ICollection, display the number of contained items.
@@ -176,7 +188,8 @@ private static string GetValueString(object value, bool isExpandable)
176188
}
177189
else
178190
{
179-
if (value.GetType() == typeof(string))
191+
// ToString() output is not the typename, so display that as this object's value
192+
if (value is string)
180193
{
181194
valueString = "\"" + value + "\"";
182195
}
@@ -215,6 +228,11 @@ private static VariableDetails[] GetChildren(object obj)
215228
{
216229
List<VariableDetails> childVariables = new List<VariableDetails>();
217230

231+
if (obj == null)
232+
{
233+
return childVariables.ToArray();
234+
}
235+
218236
PSObject psObject = obj as PSObject;
219237
IDictionary dictionary = obj as IDictionary;
220238
IEnumerable enumerable = obj as IEnumerable;
@@ -223,59 +241,55 @@ private static VariableDetails[] GetChildren(object obj)
223241
{
224242
if (psObject != null)
225243
{
244+
// PowerShell wrapped objects can have extra ETS properties so let's use
245+
// PowerShell's infrastructure to get those properties.
226246
childVariables.AddRange(
227247
psObject
228248
.Properties
229249
.Select(p => new VariableDetails(p)));
230250
}
231-
else if (dictionary != null)
251+
else
232252
{
233-
childVariables.AddRange(
234-
dictionary
235-
.OfType<DictionaryEntry>()
236-
.Select(e => new VariableDetails(e.Key.ToString(), e.Value)));
237-
}
238-
else if (enumerable != null && !(obj is string))
239-
{
240-
int i = 0;
241-
foreach (var item in enumerable)
242-
{
243-
childVariables.Add(
244-
new VariableDetails(
245-
string.Format("[{0}]", i),
246-
item));
247-
248-
i++;
249-
}
250-
}
251-
else if (obj != null)
252-
{
253-
// Object must be a normal .NET type, pull all of its
254-
// properties and their values
255-
Type objectType = obj.GetType();
256-
var properties =
257-
objectType.GetProperties(
258-
BindingFlags.Public | BindingFlags.Instance);
259-
260-
foreach (var property in properties)
261-
{
262-
try
253+
// We're in the realm of regular, unwrapped .NET objects
254+
if (dictionary != null)
255+
{
256+
// Buckle up kids, this is a bit weird. We could not use the LINQ
257+
// operator OfType<DictionaryEntry>. Even though R# will squiggle the
258+
// "foreach" keyword below and offer to convert to a LINQ-expression - DON'T DO IT!
259+
// The reason is that LINQ extension methods work with objects of type
260+
// IEnumerable. Objects of type Dictionary<,>, respond to iteration via
261+
// IEnumerable by returning KeyValuePair<,> objects. Unfortunately non-generic
262+
// dictionaries like HashTable return DictionaryEntry objects.
263+
// It turns out that iteration via C#'s foreach loop, operates on the variable's
264+
// type which in this case is IDictionary. IDictionary was designed to always
265+
// return DictionaryEntry objects upon iteration and the Dictionary<,> implementation
266+
// honors that when the object is reintepreted as an IDictionary object.
267+
// FYI, a test case for this is to open $PSBoundParameters when debugging a
268+
// function that defines parameters and has been passed parameters.
269+
// If you open the $PSBoundParameters variable node in this scenario and see nothing,
270+
// this code is broken.
271+
int i = 0;
272+
foreach (DictionaryEntry entry in dictionary)
263273
{
264274
childVariables.Add(
265275
new VariableDetails(
266-
property.Name,
267-
property.GetValue(obj)));
276+
"[" + i++ + "]",
277+
entry));
268278
}
269-
catch (Exception)
279+
}
280+
else if (enumerable != null && !(obj is string))
281+
{
282+
int i = 0;
283+
foreach (var item in enumerable)
270284
{
271-
// Some properties can throw exceptions, add the property
272-
// name and empty string
273285
childVariables.Add(
274286
new VariableDetails(
275-
property.Name,
276-
string.Empty));
287+
"[" + i++ + "]",
288+
item));
277289
}
278290
}
291+
292+
AddDotNetProperties(obj, childVariables);
279293
}
280294
}
281295
catch (GetValueInvocationException)
@@ -290,6 +304,61 @@ private static VariableDetails[] GetChildren(object obj)
290304
return childVariables.ToArray();
291305
}
292306

307+
private static void AddDotNetProperties(object obj, List<VariableDetails> childVariables)
308+
{
309+
Type objectType = obj.GetType();
310+
var properties =
311+
objectType.GetProperties(
312+
BindingFlags.Public | BindingFlags.Instance);
313+
314+
foreach (var property in properties)
315+
{
316+
// Don't display indexer properties, it causes an exception anyway.
317+
if (property.GetIndexParameters().Length > 0)
318+
{
319+
continue;
320+
}
321+
322+
try
323+
{
324+
childVariables.Add(
325+
new VariableDetails(
326+
property.Name,
327+
property.GetValue(obj)));
328+
}
329+
catch (Exception ex)
330+
{
331+
// Some properties can throw exceptions, add the property
332+
// name and info about the error.
333+
if (ex.GetType() == typeof (TargetInvocationException))
334+
{
335+
ex = ex.InnerException;
336+
}
337+
338+
childVariables.Add(
339+
new VariableDetails(
340+
property.Name,
341+
new UnableToRetrievePropertyMessage(
342+
"Error retrieving property - " + ex.GetType().Name)));
343+
}
344+
}
345+
}
346+
293347
#endregion
348+
349+
private struct UnableToRetrievePropertyMessage
350+
{
351+
public UnableToRetrievePropertyMessage(string message)
352+
{
353+
this.Message = message;
354+
}
355+
356+
public string Message { get; }
357+
358+
public override string ToString()
359+
{
360+
return "<" + Message + ">";
361+
}
362+
}
294363
}
295364
}

test/PowerShellEditorServices.Test/Debugging/DebugServiceTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -201,14 +201,14 @@ await this.debugService.SetBreakpoints(
201201
Assert.True(objVar.IsExpandable);
202202

203203
var objChildren = debugService.GetVariables(objVar.Id);
204-
Assert.Equal(2, objChildren.Length);
204+
Assert.Equal(9, objChildren.Length);
205205

206206
var arrVar = variables.FirstOrDefault(v => v.Name == "$arrVar");
207207
Assert.NotNull(arrVar);
208208
Assert.True(arrVar.IsExpandable);
209209

210210
var arrChildren = debugService.GetVariables(arrVar.Id);
211-
Assert.Equal(4, arrChildren.Length);
211+
Assert.Equal(11, arrChildren.Length);
212212

213213
var classVar = variables.FirstOrDefault(v => v.Name == "$classVar");
214214
Assert.NotNull(classVar);

0 commit comments

Comments
 (0)