views:

309

answers:

5

Here is a simplified version of what I'm trying to do:

Without having multiple if..else clauses and switch blocks, can I mimic the behavior of Javascript's eval() shudder to instantiate a class in C#?

// Determine report orientation -- Portrait or Landscape
// There are 2 differently styled reports (beyond paper orientation)

string reportType = "Portrait";
GenericReport report;
report = new eval(reportType + "Report()");  // Resolves to PortraitReport()

The need stems from the fact that I have 6 types of Crystal Reports (that do the same thing, but look drastically different) for 50 states. There are 3 styles each, rather than entertain the notion of a giant switch block with nested if..else statements determining which of 900 reports to use, I was hoping for an eval-like solution.

+3  A: 

You could use Activator.CreateInstance("myAssembly", "PortrainReport");. Although the more readable way would be to create a Portrait Factory, which would create the correct type for you.

Yuriy Faktorovich
Maybe I'm not understanding the way that would work, so let me add this to see if it changes your answer:That same line could also turn around and evaluate to "LandscapeReport()" based on the value of the string reportType. Does your idea still work with that one line of code?
C. Griffin
Ah, now I see that you guys are talking about the same thing. I'm gonna check into this and post back.
C. Griffin
I'm still having a bit of trouble with Activator... I'm starting to think that the Activator does not want to create an instance of a CrystalReports report object in this manner, as the program is throwing a "TypeLoadException was unhandled" error at runtime on the line where I'm trying to instantiate the report. I think I might have to look at the Factory possibility.
C. Griffin
This worked for me after using the overloaded method Activator.CreateInstance(Type t) -- having already gotten the type from a dynamically generated string! Thank you, thank you!
C. Griffin
+1  A: 

Look at the Activator create instance method

Gregoire
This suggestion is looking a little closer to what I was hoping to find... Does it matter that the class is an .rpt file? (Which is just a class that inherits CrystalDecisions.CrystalReports.Engine.ReportClass)
C. Griffin
+1  A: 

All the classes will need to adhere to an interface. Then make an Generic Method which will be your eval and requires that interface. Here is an example of this (call the Usage static to see it in action):

public interface IOperation
{
    string OutputDirection { get; set; }   
};

public class MyOperation: IOperation
{
    public string OutputDirection { get; set; }
}   

public static class EvalExample
{

    public static T Eval<T>( string direction ) where T : IOperation
    {
            T target = (T) Activator.CreateInstance( typeof( T ) );

            target.OutputDirection = direction;

            return target; 
    }

    // Example only
    public static void Usage()
    {

        MyOperation mv = Eval<MyOperation>( "Horizontal" );

        Console.WriteLine( mv.OutputDirection ); // Horizontal
    }

}
OmegaMan
I probably should have pointed out that the OutputDirection in my example isn't really a variable or part of a class, but actually ends up determining which one of (2) reports is to be used.
C. Griffin
+1  A: 

Using the factory pattern, and reflection (as explained in this blog post), you would get:

static void Main(string[] args)
{
    ReportFactory<Report> factory = new ReportFactory<Report>();

    Report r1 = factory.CreateObject("LandscapeReport");
    Report r2 = factory.CreateObject("PortraitReport");

    Console.WriteLine(r1.WhoAmI());
    Console.WriteLine(r2.WhoAmI());
}

Which would output "Landscape" and "Portrait", respectivley.

Of course, for the plumbing, you need an interface that all your reports are based off of (which I assume you already have).

For this example:

public interface Report
{
    string WhoAmI();
}

And the two implemenations:

public class PortraitReport : Report
{
    public string WhoAmI()
    {
        return "Portrait";
    }

}

public class LandscapeReport : Report
{
    public string WhoAmI()
    {
        return "Landscape";
    }
}

The secret is in the ReportFactory, which uses Reflection to see what other classes are based on Report, and automatically register them for use, which I think is pretty cool:

public class ReportFactory<Report>
{
    private Dictionary<string, Type> reportMap = new Dictionary<string, Type>();

    public ReportFactory()
    {
        Type[] reportTypes = Assembly.GetAssembly(typeof(Report)).GetTypes();
        foreach (Type reportType in reportTypes)
        {
            if (!typeof(Report).IsAssignableFrom(reportType) || reportType == typeof(Report))
            {
                // reportType is not derived from Report
                continue; 
            }

            reportMap.Add(reportType.Name, reportType);
        }
    }

    public Report CreateObject(string ReportName, params object[] args)
    {
        return (Report)Activator.CreateInstance(reportMap[ReportName], args);
    }
}

So now all you have to do is just add any new implementations of Report in your assembly, and they will be available to the factory with no extra coding or changing other code files.

GalacticJello
You're my last hope, I'm going to try this and report back! Thanks, everyone -- your answers were fast and varied (which is helpful!).
C. Griffin
I didn't have to get this fancy this time around, but this looked like an elegant solution.
C. Griffin
+1  A: 

As people specified above, you can use Activator class to create an instance of the class by its text name.

But, there is one more option. When you told about using eval like function in c# i assumed, you not only want to create an instance of the class by its text name, but also fill it with properties from the same string.

For this purpose you need to use deserialization.

Deserialization converts string like representation of the class into its instance and restoring all its properties that was specified in the string.

Xml serialization. Its using XML file for converting into instance. Here is small example:

public class Report1 
{
 public string Orientation {get;set;}
 public string ReportParameter1 {get;set;}
 public string ReportParameter2 {get;set;}
}

Above is the class that you want to instantiate and fill with parameters by string line. Below is XML that can do that:

<?xml version="1.0"?>
<Report1>
  <Orientation>Landscape</Orientation>
  <ReportParameter1>Page1</ReportParameter1>
  <ReportParameter2>Colorado</ReportParameter2>
</Report1>

To create an instance from the file use System.Xml.Serialization.XmlSerializer :

string xml = @"<?xml version=""1.0""?>
                <Report1>
                  <Orientation>Landscape</Orientation>
                  <ReportParameter1>Page1</ReportParameter1>
                  <ReportParameter2>Colorado</ReportParameter2>
                </Report1>";

            ///Create stream for serializer and put there our xml
            MemoryStream  str = new MemoryStream(ASCIIEncoding.ASCII.GetBytes(xml));

            ///Getting type that we are expecting. We are doing it by passing proper namespace and class name string
            Type expectingType = Assembly.GetExecutingAssembly().GetType("ConsoleApplication1.Report1");

            XmlSerializer ser = new XmlSerializer(expectingType);

            ///Deserializing the xml into the object
            object obj = ser.Deserialize(str);

            ///Now we have our report instance initialized
            Report1 report = obj as Report1;

In this way you can prepare appropriate xml as string concatenation. That xml will contain all parameters for your report.

Then, you can convert it into the proper type.

Andrey Tagaew
This looks as though it might work, but it still ends up leaving me with one of my original problems -- which is, (in your final line of code) you are assuming that my object will be only 1 type, as I would have to account for several hundred different types.
C. Griffin
Your example gave me what I needed to properly implement one of the solutions listed above -- the Activator.CreateInstance(Type t) method. Your line of code instantiating expectingType helped tremendously -- Thank you!
C. Griffin