0

I wish to know if there is a way for a method to determine, at run time, the type that invokes it.

[UPD2] For this particular case I'm interested in the Type of the caller only, not the name of the method, so, other similar questions like How can I find the method that called the current method? or Retrieving the calling method name from within a method doesn't solve my problem.

For the sake of simplicity, let's assume I have a class named AnimalContainer. At its initialization its constructor will receive a list of allowed types: The types who are allowed to register animals to the container.

The goal of this class (something like an IoC Container) is to serve as a container for any type who wish to retrieve an instance of a particular animal. The restriction is that only an arbitrary set of types should be able to register animals to the container. Then, if a non-allowed type calls the method, the method would return an InvalidOperationException

Expecting a Type or an object parameter:

void RegisterAnimal(Type callerType, Animal animal) {...}
// Or
void RegisterAnimal(object invoker, Animal animal) {...}

These won't work because the invoker is not restricted to specify its own type (or instance). For example:

// Inside class A:
animalContainer.RegisterAnimal(typeof(B), anyAnimal);

[UPD1] I've seen many answers stating that this seems so be a bad approach. If you consider so, would you suggest another way? In general terms, What I'm trying to achieve is to code defensively. I surely can have, by convention, the set of Types that are allowed to register animals in that container. What would happen, is any other developer, who aren't aware of this convetions, try to register an animal inside a class doesn't suppose to be doing that? It will be doing something that the design isn't expecting, so, most probably will end up by introducing some erros. This is just the simpler example I could find (The AnimalContainer) but it can be extended to a huge set of situations:

  1. Having a set of controllers clases that would register dependencies into an IoC Container.
  2. Having a MessageContainer that register publishers and subscriptor for messages between different types (decouple the fact that an event subscriber should know who is publishing the event). And then specifying that only a set of types can publish a given message. Ex: Any class can listen for NewAnimalArrive message, but only a specific set of clases can fire that event.
Community
  • 1
  • 1
mdarefull
  • 829
  • 2
  • 14
  • 24
  • 1
    That's a big old code smell right there. And it isn't a dupe, as he's looking for the *type* of the caller. –  Jun 24 '15 at 19:58
  • 1
    It's a weird and very suspect requirement. You usually want to (and easily can) constrain the Types being registered. Who does the registering should be of no concern to the registrar. – H H Jun 24 '15 at 20:01

3 Answers3

1

Well, you could use reflection to access the stack trace - but that's slow, and in some cases I wouldn't be surprised if the JIT skipped some stack frames due to inlining.

One option is to make the methods internal, and only include the "allowed" types within the same assembly. Or make the methods private, and only include the "allowed" types as nested types within the outer type.

There isn't anything more fine-grained than that though - if you need more than the "allowed" types within the assembly, you should think about your trust model... how much control do you have over the other code that ends up in your assembly? If other people you don't trust can change that code, it's game over anyway, and they can undo any protection you add.

If you're trying to prevent accidentally doing the wrong thing, you could use a Roslyn Code Diagnostic, potentially... but fundamentally there's only so far this sort of thing can take you.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Mr @JonSkeet: The AnimalContainer is the sink node in the references graph. Including the "animal's Publisher" on that node would break that property posible ending with cycle references. – mdarefull Jun 24 '15 at 20:51
  • @mdarefull: Sorry, I don't follow that at all. By references do you mean assemy references or object references? – Jon Skeet Jun 24 '15 at 20:58
  • Assembly reference, of course. The AnimalContainer should be accesible by every other object and the Factory Classes (the set of allowed types) should have references to all the objects. Putting both on the same assembly would create a cycle reference. Anyway, you were right when assumming what I'm trying to acchieve. I'm trying to code defensively. Surely I can create a "convention" about which types will register instances to the container, but what would happen if a new developer that doesn't know about this convention would try to publish instances in places and moments that aren't allowed – mdarefull Jun 24 '15 at 21:05
  • @mdarefull: If you can't trust new developers to read your documentation or to talk with the rest of the team - assuming this is your internal code - then there are bigger problems. – Jon Skeet Jun 24 '15 at 21:06
  • or simply aren't conceived by the original design of the solution. This would break something and would be very difficult to find the cause. By code-restricting the allowed types I avoid that situation. – mdarefull Jun 24 '15 at 21:07
  • @mdarefull: Well without knowing more context it's hard to help you towards a less brittle design, but basically the feature you're asking for doesn't exist in C# - the access control you have is just in terms of private/protected/internal/public. – Jon Skeet Jun 24 '15 at 21:09
  • One can never be enough cautious right? In big Projects errors like that tends to happen very frequently. Trying to code defesinvely is my way to avoid as much as I can. Sometimes a "No" is also a valid answer. So I will mark your answer as the answer to my solution. – mdarefull Jun 24 '15 at 21:16
0

StackTrace stackTrace = new StackTrace(); Console.WriteLine(stackTrace.GetFrame(1).GetMethod().Name);

  • Sorry but that gives me the MethodName, what I need is the Type of the instance that invoques my method. – mdarefull Jun 24 '15 at 20:48
0

This requirement smells bad, and I suspect that you'd be better off doing things differently. That said, the following code can generally get you the caller's type. I strongly recommend against it for various reasons (including the fact that it's slow, slightly error prone and did I mention that it's slow?

Please think about what you're doing before you consume the following code.

[MethodImpl(MethodImplOptions.NoInlining)]
private static Type GetCallerType()
  int skipFrames = 2; //Assumes that you are calling this from the method being invoked by the caller. Add stack frames to skip for each method removed, and make sure each one is marked with [MethodImpl(MethodImplOptions.NoInlining)]
  Type declaringType;
  do
  {
    MethodBase method = new StackFrame(skipFrames, false).GetMethod();
    declaringType = method.DeclaringType;
    skipFrames++;
  } while (declaringType.Module.Name.Equals("mscorlib.dll", StringComparison.OrdinalIgnoreCase));
  return declaringType;
}

Code is based on NLog's source code

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
willaien
  • 2,647
  • 15
  • 24
  • Would you, please, design a different way for that requirement? Again. supose you have an AnimalContainer class (like a magazine), and then a set of, let's say, animal's stores. Only the Animal's stores can register animals to your "magazine", and then, any other object (being it a person, another store, a restaurant) can get those animal's instances. How would you solve that requirement? What approach would you take? – mdarefull Jun 24 '15 at 20:47
  • @mdarefull: Let any caller register any type, then constrain the actual Type to be added to be of the expected supertype. – willaien Jun 24 '15 at 20:48
  • I'm sorry but I think you don't understand the problem. In fact, I don't care about the Types being registered, but about the Class that can register types. – mdarefull Jun 24 '15 at 20:55
  • Why does it matter who can register a type? – willaien Jun 24 '15 at 20:56
  • Look the AnimalContainer as an Animal's Store. You want only the breeder to sells animals on the store. You don't want that anyone sells an animal. As a more valuable problem. Imagine you have an IoC container. Because you are coding defensively you don't want that anyone (but a selected set of "special" class) be able to register dependencies on the container. If another developer comes after you, and doesn't know that "conventions" then he may try to register an instance and posible breaking your Project. – mdarefull Jun 24 '15 at 20:59
  • And I can break your application by simply throwing in a `throw new Exception()`, I still don't see the value in restricting who can register things in an IOC world. If it matters that much to you, hoard the reference to the IOC container and make it a private variable of the thing allowed to add references. Or some such. (Or wrap it in a readonly container to pass around) – willaien Jun 24 '15 at 21:01
  • :) Anyone can mistakenly asume because a method is public that can be called anywhere, certainly in very big projects. The developer that make the throw new Exception() is simply an idiot. – mdarefull Jun 24 '15 at 21:12