How you can get all the classes in a given file
It is possible to get all the classes in a given .cs file at runtime under three circumstances:
- You parse the .cs file at build time and add the list of classes in it to your .dll (probably as a resource)
- You ship the .cs file itself and parse it at runtime
- You compile with debuggng information and ship the .pdb file
Shipping a .pdb or .cs for this purpose seems like a bad idea, so I would probably go with solution #1. The rest of this answer will tell how to do it.
A. Configuring MSBuild to include class name lists in assembly's resources
To do this:
Create a custom MSBuild task project. Reference the Microsoft.Build.Framework
assembly and create a custom subclass of Microsoft.Build.Framework.Task
that takes an input .cs file, parses it to extract class names (including namespaces), and writes the full class names separated by commas to to an output file.
Add a Target referencing your custom MSBuild Task to your main .csproj. Use the Compile
item group for input and put the output in the EmbeddedResources
item group, giving it the output file a name like "$(IntermediateOutputPath)
%(FileName)%(Extension).classList`".
Add a PropertyGroup
to your .csproj that appends your custom target to the CoreCompileDependsOn
property.
B. Using the class name lists to find the classes for a given file
If you do this, your .classList files will be built for every file in your project marked "Compile" (basically all your .cs or .vb files), and then embedded into the executable.
Now finding all the classes in a given file is easy:
var classesInFile =
from className in assembly.GetManifestResourceStream(fileName + ".classList")
select assembly.GetType(className)
Additional details on some of the build steps
Here is what the build task would look like:
public BuildClassListsTask : Task
{
public ITaskItem[] CompileFiles;
public ITaskItem[] ClassListFiles;
public override bool Execute()
{
for(int i=0; i<ClassListFiles.Count; i++)
File.WriteAllText(ClassListFiles[i].ItemSpec,
ExtractClassNames(
File.ReadAllText(CompileFiles[i].ItemSpec)));
}
public string ExtractClassNames(string codeFile)
{
string namespacePrefix = "";
var classNames = new List<string>();
foreach(string line in codeFile.Split("\r\n").Select(line => line.Trim()))
if(line.StartsWith("namespace "))
namespacePrefix += line.Substring("namespace ".Length).Trim() + ".";
else if(line.StartsWith("class "))
classNames.Add(line.Substring("class ".Length).Trim());
return string.Join(",", classNames);
}
}
Here is what the Target would look like in your .csproj (or .vbproj):
<UsingTask TaskName="BuildClassListsTask"
AssemblyFile="BuildClassLists.dll" />
<Target Name="BuildClassLists"
Inputs="@(Compile)"
Outputs="@(EmbeddedResources)">
<ItemGroup>
<ClassListFiles Include="@(Compile->$(IntermediateOutputPath)`%(FileName)%(Extension).classList)" />
<EmbeddedResources Include="@(ClassListFiles)" />
</ItemGroup>
<BuildClassListsTask
CompileFiles="@(Compile)"
ClassListFiles="@(ClassListFiles)" />
</Target>
And last but not least, the CoreCompileDependsOn setting:
<PropertyGroup>
<CoreCompileDependsOn>$(CoreCompileDependsOn);BuildClassLists</CoreCompileDependsOn>
</PropertyGroup>
The above code is just typed off the top of my head, so it may not be exactly correct. Hopefully it will work for you, though.
Final notes
If you want to include additional data available at runtime in your .cs file, just put it in comments and suitably modify the ExtractClassNames
method above to extract the additional data from the comments and include it in its output.
Another possibility would be to use the above technique but change the Execute
method to loop through all the compiled files in your application and create a single output file containing all the configuration data for all the files in your application, probably in an XML format. If you do this, you would include just the single output file in the EmbeddedResources
item group, and you could hard-code its name.
A third possibility would be to attach your configuration data as attributes of an arbitrary class in the .cs file and let the .classNames
file help you associate it with all classes in the file.