views:

286

answers:

5

I have a file with a dozen classes and i use reflection to generate data from it. I was thinking it may be cool if i had a way to configure all of the classes in that file at once. How might i do this?

-edit- everyone seems confused. I posted a possible solution below.

I want to attach data to a file. While using reflection i would like to see if data exist for that file and change behavior based on data set. In the example solution i use reflection to look for MyConfig class. I can use attributes to set the configuration data and use that to change the behavior of my code. This will work on a per namespace bases. I would like to make it a per file bases. With this solution i MUST reserve a class name or i need to search all changes and see if it is tagged as a configuration class. What other solutions can i do? I could possibly use app.config but i prefer to set the data in the source file and would like to NOT set it by tagging every class with attributes by hand (or force them to inherit).

A: 

You could have a class with attributes to use as configuration. Here is some sample code to find a class.

        var type = typeof(SomeClass);
        var assembly = type.Assembly;
        var configType = assembly.GetType(type.Namespace + ".MyConfig");
        //configType.GetCustomAttributes(...)
acidzombie24
+2  A: 

This sounds like a really unconventional approach to configuration. What benefits do you see from doing this?

The advantages to using the System.Configuration namespace include:

  • You don't need to rebuild your application for configuration changes
  • Substantial support from the existing BCL, Visual Studio and (for ASP.NET) IIS itself
  • Ability to use strongly-typed custom configuration sections
  • It's what anyone else maintaining your application would expect

I'd also question the need to have more than one class per file.

Jeremy McGee
Basically i have 20 class files and i use reflection with them. Instead of tagging ALL 20 with attributes i want to just set them all once with a configuration. I know i could use inheritance and tag the base file but i prefer not to.
acidzombie24
+4  A: 

There is no appropriate reflection object that describes (at runtime) a "file with a dozen classes"; only classes, assemblies, and (by inspection against types) namespaces. You could use an assembly-level attribute, but frankly I'm not liking this approach much. But something like:

[assembly:MyMagicAttribute("My.Namespace", typeof(MyConfigClass))]

might work. You'd still need to read the attribute manually.

I would stick to regular configuration approaches, or if you wan't something more fluid, perhaps use an ironpython / ironruby script that you embed and execute.

Marc Gravell
+1. Now i know i cant do this on a per file level. Interesting, embed a script. If it becomes more complex i could do that but ATM the complexity i need is these 20 classes in this file are have THIS attribute so consider it when using reflection to tweak the output.
acidzombie24
+2  A: 

Finding all classes in a .cs file is not a viable solution because once you compile the code, your .cs file does not exist. All of the classes you are looking for would need to be in the same namespace, this is how you will need to find classes, not by file.

if they are all in the same namespace you can use the following method to get all of the classes:

static List<string> GetAllClasses(string nameSpace)
{
    Assembly asm = Assembly.GetExecutingAssembly();
    List<string> namespaceList = new List<string>();
    List<string> returnList = new List<string>();
    foreach (Type type in asm.GetTypes())
    {
        if (type.Namespace == nameSpace)
            namespaceList.Add(type.Name);
    }

    foreach (String className in namespaceList)
        returnList.Add(className);
    return returnList;
}

or using linq (I got this and answer in this post)

string @namespace = "...";
var q = from t in Assembly.GetExecutingAssembly().GetTypes()
        where t.IsClass && t.Namespace == @namespace
        select t;
q.ToList().ForEach(t => Console.WriteLine(t.Name));

You can also use interfaces: http://msdn.microsoft.com/en-us/library/ms173156.aspx

Russ Bradberry
I like this approach. It's clean and doesn't require you to tag your classes with an attribute - although it would help immensely, especially since your class names are all different. I would try to organize these classes into at least the same namespace, go ahead and tag your classes with an attribute. It takes a little bit of effort, but you could be spending more time continuing to look for answers when you have a couple of good ones starting you in the face. :)
dboarman
+2  A: 

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:

  1. You parse the .cs file at build time and add the list of classes in it to your .dll (probably as a resource)
  2. You ship the .cs file itself and parse it at runtime
  3. 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:

  1. 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.

  2. 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`".

  3. 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.

Ray Burns
Beautiful answer. This is a classic right here.
acidzombie24