views:

142

answers:

6

I want to parse a c# file. The only thing I want is to determine if it contains a property with a specific name; just a simple true/false response. Or rather, since I'd checking for more than one property in each run, extracting a list of property names could be helpful

I thought that I could create an elegant solution using the CodeDomProvider functionality (f# example):

use reader = new StreamReader(existingFile)
let codeProvider = new CSharpCodeProvider()
let codeUnit = codeProvider.Parse(reader)

Unfortunately, the Parse function is not implemented for the CSharpCodeProvider. Is there a way to get a CodeCompileUnit from a source file? Or is there another elegant way? (I had hoped to avoid regular expressions on this)?

Edit: I'm going to use this for automatic code generation. Basically, I'm going to generate a partial class in file xyz.partial.cs. This will generate a skeleton property. But if I want to change the implementation of a property, I will cut that property and paste it into the hand coded xyz.cs. When recreating the generated class, I want it to skip generating properties that I have moved to the hand-coded file.

Therefore, reflection is out of the question, because reflection will tell me that the property does indeed exists, but not if it is defined in the one or the other file.

A: 

Found this, but have no experience -- good or bad -- with it: http://www.codeproject.com/KB/recipes/codedomparser.aspx

Jay
A: 

Sometimes RegEx is the only elegant solution. This should be what you're looking for. It will give you the names of every property in the code file, and nothing more:

(?:"(?:(?:(?:\\.)|[^"\\\r\n])*)"|'(?:(?:(?:\\.)|[^'\\\r\n])*)'|@"(?:(?:(?:"")|[^"])*)")|(?:(?://.*)|(?:/\*(?:(?:[^*]|\*(?!/))*)\*/))|(?:[\w?<>]\s+(\w+)\s*\{\s*(?:get|set)\s*[{;])

It will not match similar code in comments or strings and needs only a small modification to return the property's type. The name appears in capture \1, but will be blank if not a true match.

Patrick
You call it "elegant" ?" Some people, when confronted with a problem, think “I know, I'll use regular expressions.” Now they have two problems "
remi bourgarel
What would you use? Nested loops and substrings? One number out of place and you have an unpredictable explosion of problems. Regex has never been a problem for me, and often gets the job done in just one line of code. Much more efficient.
Patrick
I realized that I probably need to use regex, and this one got me started, but there are some things that it does not match: Properties with generic or nullable types (because the name of the type doesn't end in a character, but a ? or a >), and it doesn't match auto properties (properties with automatic backing field)
Pete
It does now, and I did a bit of testing to make sure. If you want to split it up, you can make one that deletes all the code you don't want until all that is left is something more easily manageable. I do agree that monster-patterns (those that take over 2 lines and contain no static strings) can be a nightmare for everyone who isn't prepared for it, but hey, that's what comments are for.
Patrick
+1  A: 

EDIT 2: Based on the added information, I'd say you're best of compiling the hand coded class and then reflecting that class. You can then generate the code for the partial class file.

EDIT: I've done some more research and it appears you are out of luck. CodeDom cannot be used to parse code. http://blogs.msdn.com/b/bclteam/archive/2005/03/16/396929.aspx

There is an example on how to create a CSharpCodeProvider instance on MSDN.

CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CodeCompileUnit ccu = provider.Parse(reader);

You can then navigate the CodeCompileUnit (more documentation on CodeCompileUnit).

Hope it helps.

Bloodsplatter
It looks like the Parse method isn't implemented by all CodeDomProvider classes. The original question makes it sound like it's not implemented by C#. Anyone know for sure?
Don Kirkby
They are implemented at runtime I think. Have you tried actually running the code? Unless it throws exceptions at runtime, I suggest you give it a try.
Bloodsplatter
That was how I started it. It gives the same result, NotImplementedException. CodeDomProvider.CreateProvider("CSharp") simply returns a CSharpCodeProvider.
Pete
Did some more researche and edited my answer.
Bloodsplatter
A: 

I think you can solve this by using PropertyInfo at runtime. It uses reflection to return all the info for a type. To get all the property names from a type, try this:

void GetMeTheProperties(object source)
{
    Type sourceType = source.GetType();

    foreach (PropertyInfo sourceProperty in sourceType.GetProperties())
    {
        int i = 1;
        Console.WriteLine("Property {0}: {1}", i, sourceProperty.Name;
    }
}

You can also determine if a specific named property is in a type by a similar method:

bool PropertyExists(string propertyName, object source)
{
    Type sourceType = source.GetType();
    return (from var s in sourceType.GetProperties() select s).Where(i => i.Name == propertyName).Any();
}
Odhran
Unfortunately, reflection is not an option, see edit for a reason why
Pete
Sorry ... misinterpreted the question.
Odhran
A: 

Is the file you are generating the code from compiled? If so, you could try creating an attribute to add to all the properties that shouldn't be copied. Then you can use reflection to read through the attributes and skip those ones.

internal class DoNotCopyAttribute: Attribute{}

// then add this to Odhran's GetMeTheProperties
bool skip=false;
foreach (System.Attribute attr in System.Attribute.GetCustomAttributes(sourceProperty)) {
  if (attr is DoNotCopyAttribute){ skip=true; break; }
}
if(skip) continue;
Patrick
A: 
var provider = CodeDomProvider.CreateProvider("c#");
var parameters = new CompilerParameters
{
    WarningLevel = 3 // for example, tune how you need
};
var result = provider.CompileAssemblyFromSource(parameters, new string[] { "source" });

if (!result.Errors.HasErrors)
{
    var assembly = result.CompiledAssembly;

    bool containsLocalAppDomain = assembly
        .GetTypes()
        .SelectMany(t => t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
        .Any(p => p.Name == "YourProperty");

    // indeed it's much better not to load compiled assembly in current appDomain, but create a new one
    var appDomain = AppDomain.CreateDomain("YourNewDomain", null, null);
    bool containsNewAppDomain = appDomain
        .GetAssemblies()
        .SelectMany(a => a
             .GetTypes()
             .SelectMany(t => t.GetProperties(BindingFlags.Instance | BindingFlags.Public)))
         .Any(p => p.Name == "YourProperty");

btw, how are you going to implement partial properties as far as the are not supported?

abatishchev