Динамическая реализация интерфейсов (C#)

Предположим, есть интерфейс:

C#
    public interface ITest
    {
        DateTime CurDate { get; set; }
        String Name { get; set; }
        MyClass ClassID { get; set; }

        void test1(string s1, DateTime d1, int i1, int i2, int i3);
    }

Основное назначение этого интерфейса - описание структуры данных, содержащей время, имя, и значение некоторого нашего класса. (Про дополнительный метод test1 будет сказано ниже).

Для тестирования создадим простой класс, хранящий целочисленный идентификатор:

C#
    public class MyClass
    {
        public MyClass(int ID) { this.id = ID; }

        public override string ToString()
        {
            return id.ToString();
        }

        private int id = 0;
    }

Предположим, что данные хранятся и обрабатываются другим классом - исполнителем. Назовем его DataObject. Два метода этого класса позволяют получить и установить значение по его имени. Напишем простую реализацию такого класса. Заодно, объявим интерфейс IDataObject, чтобы явно указать, что наш класс обрабатывает методы getObject и setObject.

C#
    interface IDataObject
    {
        object getObject(string name);
        void setObject(string name, object value);
    }

    public class DataObject : IDataObject
    {
        private Dictionary<string, object> list = new Dictionary<string, object>(); 

        public DataObject()
        {
            Console.WriteLine("DataObject.constructor");
            list.Add("CurDate", DateTime.Now);
            list.Add("Name", "test string");
            list.Add("ClassID", new MyClass(1));
        }

        public object getObject(string name)
        {
            Console.WriteLine("getObject: "+name);
            return list[name];
        }

        public void setObject(string name, object value)
        {
            Console.WriteLine("setObject: "+name+ " = "+ value);
            list[name] = value;
        }

        public void test1(string s1, DateTime d1, int i1, int i2, int i3)
        {
            ITest t1 = (ITest)this;
            t1.Name = s1;
            t1.CurDate = d1;
            Console.WriteLine(i1);
            Console.WriteLine(i2);
            Console.WriteLine(i3);
        }
    }

Посмотрим как бы выглядела реализация интерфейса ITest, если бы мы писали ее сами с использованием класса DataObject:

C#
    class TestDataObject : DataObject, ITest
    {
        public DateTime CurDate
        {
            get { return (DateTime)getObject("CurDate"); }
            set { setObject("CurDate", value); }
        }

        public string Name 
        {
            get { return (string)getObject("Name"); }
            set { setObject("Name", value); }
        }

        public MyClass ClassID
        {
            get { return (MyClass)getObject("ClassID"); }
            set { setObject("ClassID", value); }
        }

        new public void test1(string s1, DateTime d1, int i1, int i2, int i3)
        {
            base.test1(s1, d1, i1, i2, i3);
        }
    }

Как видим описание всех свойств выглядят одинаково - обращение к методу getObject исполнителя для получения значения и к методу setObject для записи значения. Этим мы и воспользуемся для динамической реализации интерфейса. Исполнителя мы сделали предком нашего класса. Во-первых это позволяет использовать интерфейс IDataObject без включения дополнительных объектов, а во-вторых, значительно упрощает реализацию обычных методов интерфейса. В ITest объявлен метод test1. В реализации этого метода вызов просто перенаправляется исполнителю. В исполнителе настоящая реализация метода и, поскольку, сам исполнитель в данном случае реализует интерфейс ITest, допустимо приведение к типу ITest и обращение к свойствам интерфейса.

Теперь уберем из проекта класс TestDataObject и будем создавать подобный класс динамически. Еще раз. У нас есть интерфейс ITest, есть исполнитель, DataObject, который реализует получение/установку свойств и вызовы методов этого интерфейса. Требуется создать экземпляр класса, который был-бы реализацией нашего интерфейса (ITest), но все вызовы перенаправлял исполнителю.

Напишем статический класс Generator, единственный доступный метод класса CreateInstance будет создавать такой экземпляр. Этот метод создает новую сборку с типом, реализующим интерфейс и унаследованным от исполнителя. Для этого типа создаются заглушки для всех методов, описанных в интерфейсе. Вот код такого класса:

И, наконец, протестируем создание экземпляра и вызовы методов

C#
            // Создаем экземпляр класса, реализующего интерфейс ITest 
            // используя как исполнитель класс DataObject
            ITest t2 = Generator.CreateInstance<ITest, DataObject>();

            // Обращение к свойствам интерфейса возврщают значения, 
            // которые мы указали в констракторе DataObject:
            Console.WriteLine(t2.Name);      // test string 
            Console.WriteLine(t2.CurDate);   // текущая дата 
            Console.WriteLine(t2.ClassID);   // ClassID = 1

            // Можно изменить эти свойства
            t2.Name = "test";
            t2.CurDate = DateTime.Now.AddYears(1);
            t2.ClassID = new MyClass(5);

            // Доступен и вызов метода
            t2.test1("test string 2", DateTime.Now.AddYears(1), 1, 2, 3);
            // В реализации метода test1 мы меняем значение t2.Name
            Console.WriteLine(t2.Name);      // test string 2


08 августа 2009

Comments
03.06.2011 11:23 Максим
 У меня такой вопрос:
 Что значит - использование классов в которых интерфейсы уже реализованы
 
04.06.2011 08:53 alexBlack
 Не понял вопроса, уточните
 
08.02.2013 05:29 Николай
 А где-же код класса Generator!? 
 knibrest@tut.by
 
11.02.2013 10:16 alexBlack
 Под кнопочкой [C# show] в конце статьи. 
 
18.01.2015 11:36 SAWER
 Eshe bi primer gde net nugnih metodov i ih nugno sozdat
 (FireFox)
 
Вы можете оставить комментарий или задать вопрос
Ваше имя:

Текст сообщения:


Copyright © 2009-2014 by