0

I have an abstract class that will need to be able to load files contained in the assembly that made an object of the class. What I could use is FileAssembly = Assembly.GetCallingAssembly() for every child of my class, but since it is a library that people can extend I want that to happen without requiring them to do this.

My setup right now is something along the lines of:

public abstract class ExternalResource : Resource, IDisposable
{
    public string File { get; private set; }
    protected Assembly FileAssembly { get; set; }

    protected ExternalResource(string id, string file)
        : base(id)
    {
        File = file;
    }

    //and s'more code
}

public class Sound : ExternalResource
{
    public Sound (string id, string file)
        : base(id, file)
    {
        //this is the line I want to be able to get rid of
        FileAssembly = Assembly.GetCallingAssembly();
    }
}

Someone using my library could make their own ExternalResource without setting the FileAssembly, which is not desirable. It gets really messy if they would inherit from a class that already inherits from ExternalResource. How can I get the assembly of the code instantiating the object? Any other way to work around it without too much changing to the existing system would be appreciated too!

Villermen
  • 815
  • 2
  • 13
  • 28

2 Answers2

1

You can use the StackTrace class from the System.Diagnostics namespace:

In the constructor of your ExternalResource class:

var stack = new StackTrace(true);
var thisFrame = stack.GetFrame(0); // ExternalResource constructor
var parentFrame = stack.GetFrame(1); // Sound constructor
var grandparentFrame = stack.GetFrame(2); // This is the one!

var invokingMethod = grandparentFrame.GetMethod();
var callingAssembly = invokingMethod.Module.Assembly;

The references to thisFrame and parentFrame are there simply to aid understanding. I would suggest that you actually walk up the stack frame and do it more robustly, rather than just assuming that it's always frame 2 that you want (which won't give the right answer if you have an additional subclass).

Steve Morgan
  • 12,978
  • 2
  • 40
  • 49
  • Sound can be inherited over again though, so it's unclear how many levels back the object is instantiated. – Villermen Mar 15 '15 at 13:41
  • 1
    What if I create an `ExtendedSound` class that inherits from `Sound`, and so on? – Michael Petito Mar 15 '15 at 13:41
  • 1
    Funnily enough, I was in the process of editing the post when you commented :-) Walking the stack until you get to a method which isn't a constructor is probably the easiest or which isn't in a type with a base type of ExternalResource. – Steve Morgan Mar 15 '15 at 13:43
  • I'm not sure that would even work, since you could be creating an `ExternalResource` inside a constructor (even a constructor of a different type inheriting from `ExternalResource`)? – Michael Petito Mar 15 '15 at 13:45
  • Yes, it has its limitations. Checking if the method is not in a subclass of ExternalResource would be an alternative. It kind of depends on the behaviour that Villerman wants to see (and why). I can understand that he might want to keep it transparent and not use a factory method. – Steve Morgan Mar 15 '15 at 14:02
  • Walking the stack till you're outside of a class derived of ExternalResource sounds most logical to me. If you could add that to your answer I will accept it. – Villermen Mar 15 '15 at 14:13
  • 2
    @Villermen Put the example code in a loop and check `invokingMethod` to see if its declaring type is assignable to `ExternalResource`. – Michael Petito Mar 15 '15 at 14:30
0

Using the calling assembly will likely frustrate someone down the road who moves their ExternalResource related code into a new separate assembly without realizing it has an assembly dependency, or wants to move their resources into a separately identified assembly (e.g. for localization).

My suggestion would be:

  • Add an explicit assembly parameter to the constructor, making it very clear it's doing something with some assembly.
  • Provide a factory method that determines the calling assembly for the user as a convenience

Your factory method could be generic if all of your derived types expect constructor parameters string id, string file and then the new explicit assembly parameter. Something like:

public static TExternalResource CreateExternalResource<TExternalResource>(string id, string file, Assembly assembly = null) where TExternalResource : ExternalResource
{

    var ResolvedAssembly = assembly ?? Assembly.GetCallingAssembly();

    return Activator.CreateInstance(typeof(TExternalResource), id, file, ResolvedAssembly) as TExternalResource;
}
Michael Petito
  • 12,891
  • 4
  • 40
  • 54
  • It was my idea to have it happen transparantly because I wanted it to be clear that wherever the file is supplied should be the assembly that it checks in. I could also have it always be the entry assembly, but I don't know how that would work when people are e.g. adding an assembly that extends the entry assembly with it's own contained files. I want it to be as easy as possible for the people using the library (otherwise they would just use the same code (currentassembly) anyway. – Villermen Mar 15 '15 at 13:37
  • I understand the convenience factor you're trying to achieve, but I still maintain that trying to guess the assembly (and not providing any other option to instead provide the assembly) is bad form for a library. – Michael Petito Mar 15 '15 at 13:43