How can I find all objects of a particular type?

Raymond Chen

Raymond

More than one customer has asked a question like this:

I’m looking for a way to search for all instances of a particular type at runtime. My goal is to invoke a particular method on each of those instances. Note that I did not create these object myself or have any other access to them. Is this possible?

Imagine what the world would be like if it were possible.

For starters, just imagine the fun you could have if you could call typeof(Secure­String).Get­Instances(). Vegas road trip!

More generally, it breaks the semantics of App­Domain boundaries, since grabbing all instances of a type lets you get objects from another App­Domain, which fundamentally violates the point of App­Domains. (Okay, you could repair this by saying that the Get­Instances method only returns objects from the current App­Domain.)

This imaginary Get­Instances method might return objects which are awaiting finalization, which violates one of the fundamental assumptions of a finalizer, namely that there are no references to the object: If there were, then it wouldn’t be finalized! (Okay, you could repair this by saying that the Get­Instances method does not return objects which are awaiting finalization.)

On top of that, you break the syncRoot pattern.

class Sample {
 private object syncRoot = new object();
 public void Method() {
  lock(syncRoot) { ... };
 }
}

If it were possible to get all objects of a particular class, then anybody could just reach in and grab your private sync­Root and call Monitor.Enter() on it. Congratuations, the private synchronization object you created is now a public one that anybody can screw with, defeating the whole purpose of having a private syncRoot. You can no longer reason about your syncRoot because you are no longer in full control of it. (Yes, this can already be done with reflection, but at least when reflecting, you know that you’re grabbing somebody’s private field called sync­Root, so you already recognize that you’re doing something dubious. Whereas with Get­Instances, you don’t know what each of the returned objects is being used for. Heck, you don’t even know if it’s being used! It might just be garbage lying around waiting to be collected.)

More generally, code is often written on the expectation that an object that you never give out a reference to is not accessible to others. Consider the following code fragment:

using (StreamWriter sr = new StreamWriter(fileName)) {
 sr.WriteLine("Hello");
}

If it were possible to get all objects of a particular class, you may find that your customers report that they are getting an Object­Disposed­Exception on the call to Write­Line. How is that possible? The disposal doesn’t happen until the close-brace, right? Is there a bug in the CLR where it’s disposing an object too soon?

Nope, what happened is that some other thread did exactly what the customer was asking for a way to do: It grabbed all existing Stream­Writer instances and invoked Stream­Writer.Close on them. It did this immediately after you constructed the Stream­Writer and before you did your sr.Write­Line(). Result: When your sr.Write­Line() executes, it finds that the stream was already closed, and therefore the write fails.

More generally, consider the graffiti you could inject into all output files by doing

foreach (StreamWriter sr in typeof(StreamWriter).GetInstances()) {
 sr.Write("Kilroy was here!");
}

or even crazier

foreach (StringBuilder rb in typeof(StringBuilder).GetInstances()) {
 sb.Insert(0, "DROP TABLE users; --");
}

Now no String­Builder is safe—the contents of any String­Builder can be corrupted at any time!

If you could obtain all instances of a type, the fundamental logic behind computer programming breaks down. It effectively becomes impossible to reason about code because anything could happen to your objects at any time.

If you need to be able to get all instances of a class, you need to add that functionality to the class itself. (GC­Handle or Weak­Reference will come in handy here.) Of course, if you do this, then you clearly opted into the “anything can happen to your object at any time outside your control” model and presumably your code operates accordingly. You made your bed; now you get to lie in it.

(And I haven’t even touched on thread safety.)

Bonus reading: Questionable value of SyncRoot on Collections.

0 comments

Comments are closed.