C #: динамически создавать экземпляры разных классов в одном и том же утверждении? - PullRequest
4 голосов
/ 09 апреля 2010

Вот упрощенная версия того, что я пытаюсь сделать:

Не имея нескольких выражений if..else и блоков переключателей, могу ли я имитировать поведение eval () * Javascript shudder для создания экземпляра класса в 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()

Необходимость проистекает из того факта, что у меня есть 6 типов отчетов Crystal Reports (которые делают то же самое, но выглядят совершенно по-разному) для 50 состояний. Есть 3 стиля каждый, вместо того, чтобы развлекать идею гигантского блока переключателей с вложенными операторами if..else, определяющими, какой из 900 отчетов использовать, я надеялся на решение, подобное eval.

Ответы [ 5 ]

3 голосов
/ 09 апреля 2010

Вы можете использовать Activator.CreateInstance("myAssembly", "PortrainReport");. Хотя более читабельный способ - создать книжную фабрику, которая создаст для вас правильный тип.

2 голосов
/ 09 апреля 2010

Как указано выше, вы можете использовать класс Activator для создания экземпляра класса по его текстовому имени.

Но есть еще один вариант. Когда вы рассказали об использовании функции eval like в c #, я предположил, что вы не только хотите создать экземпляр класса по его текстовому имени, но и заполнить его свойствами из той же строки.

Для этого вам нужно использовать десериализацию.

Десериализация преобразует строковое представление класса в его экземпляр и восстанавливает все его свойства, указанные в строке.

XML сериализация. Он использует XML-файл для преобразования в экземпляр. Вот небольшой пример:

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

Выше представлен класс, который вы хотите создать и заполнить параметрами строкой. Ниже приведен XML, который может это сделать:

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

Чтобы создать экземпляр из файла, используйте 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;

Таким образом, вы можете подготовить соответствующий xml как конкатенацию строк. Этот xml будет содержать все параметры вашего отчета.

Затем вы можете преобразовать его в соответствующий тип.

1 голос
/ 09 апреля 2010

Используя фабричный шаблон и отражение (как описано в этом блоге po st), вы получите:

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());
}

Что бы вывести "Пейзаж" и "Портрет", соответственно.

Конечно, для сантехники вам нужен интерфейс, на котором основаны все ваши отчеты (который, я полагаю, у вас уже есть).

Для этого примера:

public interface Report
{
    string WhoAmI();
}

И две реализации:

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

}

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

Секрет кроется в ReportFactory, который использует Reflection, чтобы увидеть, какие другие классы основаны на Report, и автоматически зарегистрировать их для использования, что я считаю довольно круто:

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);
    }
}

Так что теперь все, что вам нужно сделать, это просто добавить любые новые реализации Report в вашу сборку, и они будут доступны на заводе без дополнительного кодирования или изменения других файлов кода.

1 голос
/ 09 апреля 2010

Все классы должны будут придерживаться интерфейса. Затем создайте Generic Method, который будет вашим eval и требует такого интерфейса. Вот пример этого ( вызовите статическое использование, чтобы увидеть его в действии ):

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
    }

}
1 голос
/ 09 апреля 2010

Посмотрите на метод создания экземпляра Activator

...