Модераторы: Partizan, gambit
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Отражение: инициализация свойств 
V
    Опции темы
mr.Anderson
Дата 6.4.2010, 19:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


iOS Lead Developer
****


Профиль
Группа: Участник Клуба
Сообщений: 3374
Регистрация: 20.12.2004
Где: далеко

Репутация: 1
Всего: 128



Задача такого плана. Есть XML-файлик, где расписаны значения некоторых свойств, которые имеются в классе. Класс в своем конструкторе должен эти значения прочитать и растолкать по свойствам, ориентируясь на имя нода (оно совпадает с именем свойства). Сейчас этим очень топорно занимается этот код:
Код

protected void LoadInfo(string xmlDataFile)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(xmlDataFile);
    SearchRadius = int.Parse(doc.SelectSingleNode("Root/SearchRadius").InnerText);
    KillRadius = int.Parse(doc.SelectSingleNode("Root/KillRadius").InnerText);
    MoveSpeed = int.Parse(doc.SelectSingleNode("Root/MoveSpeed").InnerText);
    CloneSpeed = int.Parse(doc.SelectSingleNode("Root/CloneSpeed").InnerText);
    CloneRadius = int.Parse(doc.SelectSingleNode("Root/CloneRadius").InnerText);
    CloneChance = int.Parse(doc.SelectSingleNode("Root/CloneChance").InnerText);
    CloneFrequency = int.Parse(doc.SelectSingleNode("Root/CloneFrequency").InnerText);
}

Вообще, а если свойств 100? Все вручную писать? Нехорошо. Тогда нужно получить список свойств, получить список нодов и спокойно распихать значения. Но возникла проблема, а как преобразовать считываемую строку к нужному типу? Если допустить, что типы могут быть разные, будь то целые, дробные, строки, логические и т.п. Необходимый тип можно узнать по типу свойства, которому присваиваем. Осталась одна задача: привести считанную строку к нужному типу. Но как? Получить тип данного свойства, найти у него метод Parse, попытаться использовать? Ну я так и попробовал. Но не смог понять, как правильно вызвать его. Мб и другой метод есть? Сейчас теоретический недописанный вариант такой:
Код

XmlDocument doc = new XmlDocument();
doc.Load(xmlDataFile);                
if (doc.DocumentElement == null)
    throw new InvalidXmlDataException(xmlDataFile);
// получим все нужные свойства и проставим их значения
XmlNodeList nodes = doc.DocumentElement.ChildNodes;
foreach (XmlNode node in nodes)
{
    PropertyInfo pInfo = typeof (Fish).GetProperty(node.Name);
    if (pInfo != null)
    {
        Type pType = pInfo.GetType();
        //if (pInfo.)
        //if (pInfo.)
        if (pInfo.PropertyType.IsValueType)
        {
            MethodInfo mInfo = pInfo.PropertyType.GetMethod("Parse");
            if (mInfo != null)
                mInfo.Invoke(, node.InnerText); // как правильно прописать вызов?
        }
    }
}

И вообще, в том ли направлении я думаю? Может, попроще есть способ?


--------------------
user posted image

user posted image
PM MAIL ICQ Skype   Вверх
KelTron
Дата 7.4.2010, 11:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 745
Регистрация: 8.10.2006
Где: Красноярск

Репутация: 11
Всего: 38



Так вроде:
Код

var mInfo = pInfo.PropertyType.GetMethod("Parse", new Type[]{typeof(string)});
object value = mInfo.Invoke(null, new object[]{node.InnerText});
pInfo.SetValue(this, value, null);




--------------------
Тысячами незримых нитей обвивает тебя Закон. Разрубишь одну - преступник. Десять - смертник. Все - Бог.
Эвенгар Салладорский, основатель Школы Тьмы.
PM MAIL   Вверх
mr.Anderson
Дата 7.4.2010, 15:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


iOS Lead Developer
****


Профиль
Группа: Участник Клуба
Сообщений: 3374
Регистрация: 20.12.2004
Где: далеко

Репутация: 1
Всего: 128



KelTron, да, все верно. Спасибо.

Если у кого еще возникнет такая проблема, вот код чтения значений свойств с XML-файла и расталкивания их по свойствам в классе (при условии одноуровневой глубины структуры XML-файла и совпадении названия нода с названием свойства):
Код

protected void LoadInfo(string xmlDataFile)
{
    XmlDocument doc = new XmlDocument();
    doc.Load(xmlDataFile);            
    if (doc.DocumentElement == null)
        throw new InvalidXmlDataException(xmlDataFile);
    // чтение свойств сделаем через отражение
    // считаем все свойства в нашем классе
    PropertyInfo[] pInfos = typeof (Fish).GetProperties(); // вместо Fish подставьте нужный тип, в котором инициализируем свойства
    foreach (PropertyInfo pInfo in pInfos) // пробежимся по всем свойствам
    {
        XmlNode node = doc.DocumentElement.SelectSingleNode(pInfo.Name);
        if (node == null)
            continue;
        // возможно, свойство - не строка, нужно отпарсить его
        object val = node.InnerText;
        if (pInfo.PropertyType.IsValueType)
        {
            MethodInfo parse = pInfo.PropertyType.GetMethod("Parse", new[] {typeof (string)}); // получим метод разбора строки
            val = parse.Invoke(null, new[] {node.InnerText}); // вызовем метод и запишем значение в нашу объектную переменную
        }
        pInfo.SetValue(this, val, null); // инициализируем значение свойства. Вместо this - объект нужного типа (в моем случае - Fish)
    }
}



--------------------
user posted image

user posted image
PM MAIL ICQ Skype   Вверх
PashaPash
Дата 7.4.2010, 16:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 9
Всего: 49



mr.Anderson, а почему не через стандартный XmlSerializer?


--------------------
PM MAIL WWW   Вверх
mr.Anderson
Дата 7.4.2010, 16:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


iOS Lead Developer
****


Профиль
Группа: Участник Клуба
Сообщений: 3374
Регистрация: 20.12.2004
Где: далеко

Репутация: 1
Всего: 128



PashaPash, я с ним вообще не сталкивался, хотя по названию уже понятно примерно, что он делает. Просто не работал, это решение кажется более простым и очевидным.


--------------------
user posted image

user posted image
PM MAIL ICQ Skype   Вверх
PashaPash
Дата 7.4.2010, 16:27 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 9
Всего: 49



mr.Anderson, 
Код

    public class Fish
    {
        public int A { get; set; }
        public int B { get; set; }
        public int C { get; set; }
    }

    class Program
    {
        static void Main()
        {
            Fish fish = new Fish();

            StringBuilder builder = new StringBuilder();

            XmlSerializer serializer = new XmlSerializer(typeof(Fish));
            serializer.Serialize(new StringWriter(builder), fish);

            Console.WriteLine(builder.ToString());


            Fish newFish = (Fish)serializer.Deserialize(new StringReader(builder.ToString()));
        }
    }

Последняя строчка - аналог LoadInfo(string xmlDataFile).


--------------------
PM MAIL WWW   Вверх
mr.Anderson
Дата 7.4.2010, 18:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


iOS Lead Developer
****


Профиль
Группа: Участник Клуба
Сообщений: 3374
Регистрация: 20.12.2004
Где: далеко

Репутация: 1
Всего: 128



Хм. Спасибо) Но как дело будет обстоять с тем, что мне не нужно инициализировать все свойства? Свойств много, и вовсе не все из них инициализируются из XML. Сериализация/десериализация как-то повлияет на поля, которые не должны быть затронуты? Или просто пометить их все как [NonSerialized] и она не будет их трогать? В таком случае методы нужно тоже запрещать сериализовать?


--------------------
user posted image

user posted image
PM MAIL ICQ Skype   Вверх
PashaPash
Дата 7.4.2010, 22:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 9
Всего: 49



mr.Anderson, методы не сериализуются.  Поля можно убирать из сериализации используя XmlIgnoreAttribute. Ну и в целом там довольно гибкий механизм.


--------------------
PM MAIL WWW   Вверх
mr.Anderson
Дата 8.4.2010, 16:34 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


iOS Lead Developer
****


Профиль
Группа: Участник Клуба
Сообщений: 3374
Регистрация: 20.12.2004
Где: далеко

Репутация: 1
Всего: 128



PashaPash, все оно здорово, только как мне загрузить свойства внутри самого класса? Т.е. вот у меня есть (как в моем примере) класс, он при работе собственного конструктора грузит свои свойства. Если использовать мою функцию, то все нормально. А если десериализацию, то куда присваивать результат десериализации? В this, понятное дело, не присвоишь. Как быть?


--------------------
user posted image

user posted image
PM MAIL ICQ Skype   Вверх
PashaPash
Дата 8.4.2010, 19:20 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 9
Всего: 49



mr.Anderson, вот в этом случае - никак. :( Просто обычно десериализация подразумевает создание нового объекта. Скорее всего сейчас создание Fish выглядит как:
Код

var fish = new Fish(pathToXmlFile);

сделать статический метод вместо конструктора, будет выглядеть как
Код

var fish = Fish.LoadFrom(pathToXmlFile);

в методе десериализовать новый объект, и возвращать его.


--------------------
PM MAIL WWW   Вверх
mr.Anderson
  Дата 8.4.2010, 20:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


iOS Lead Developer
****


Профиль
Группа: Участник Клуба
Сообщений: 3374
Регистрация: 20.12.2004
Где: далеко

Репутация: 1
Всего: 128



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

Кстати, у меня тут получилось реализовать шикарную штуку) Я сделал так, что если XML-файлик имеет структуру, описанную выше, (которой каждый нод имеет то же имя, что и соответствующее свойство в нужном классе, а содержимое нода - предполагаемое значение этого свойства), то можно теперь использовать статический класс, который для любого объекта любого типа, наследуемого от простенького интерфейса, может загрузить данные из ассоциированного с этим классом XML-файла и может автоматически растолкать все значения по нужным свойствам. Думаю, очень удобно) Привожу решение для тех, кому это может пригодиться.

Интерфейс:
Код

public interface ILoadable
{
    string XmlPropertyData { get; }
}

Пример класса, который может загружать свои свойства:
Код

public sealed class Map : ILoadable
{
    public int MinWidth { get; private set; }
    public int MinHeight { get; private set; }
    public int MaxWidth { get; private set; }
    public int MaxHeight { get; private set; } 
    public int Width { get; private set; }
    public int Height { get; private set; }
    public string XmlPropertyData { get; private set; }

    public Map(int width, int height)
    {
        XmlPropertyData = "map.xml";
        Loader.LoadProperties(this); // <-- загрузка свойств
        Width = width > MinWidth ? (width < MaxWidth ? width : MaxWidth) : MinWidth;
        Height = height > MinHeight ? (height < MaxHeight ? height : MaxHeight) : MinHeight;
    }
}

Реализация статического загрузчика:
Код

public static class Loader
{
    public static void LoadProperties(ILoadable instance)
    {
        // загрузим документ
        XmlDocument doc = new XmlDocument();
        doc.Load(instance.XmlPropertyData);
        if (doc.DocumentElement == null)
            throw new InvalidXmlDataFile(instance.XmlPropertyData);
        // получим свойства, ориентируясь на тип переданного объекта, и обработаем их            
        PropertyInfo[] pInfos = instance.GetType().GetProperties();
        foreach (PropertyInfo pInfo in pInfos)
        {
            // попытаемся найти нод с тем же именем, какое у свойства
            XmlNode node = doc.DocumentElement.SelectSingleNode(pInfo.Name);
            if (node != null)
            {
                // получим значение нода
                object val = node.InnerText;
                // если нод - числовой тип, то придется отпарсить его
                if (pInfo.PropertyType.IsValueType)
                {
                    // найдем метод преобразования, если он найден - преобразуем считанное значение свойства
                    MethodInfo mInfo = pInfo.PropertyType.GetMethod("Parse", new[] {typeof (string)});
                    if (mInfo != null)
                        val = mInfo.Invoke(null, new[] {val});
                }
                // инициализируем свойство в переданном объекте
                pInfo.SetValue(instance, val, null);
            }
        }
    }
}

Содержимое XML-файла для этого случая:
Код

<?xml version="1.0" encoding="utf-8" ?> 
<MapProperties>
  <MaxWidth>1920</MaxWidth> 
  <MinWidth>320</MinWidth> 
  <MaxHeight>1080</MaxHeight> 
  <MinHeight>640</MinHeight> 
</MapProperties>

Имхо, очень удобно) Можно теперь прописывать классы, свойства которых можно исправлять без перекомпиляции. Универсально для любых проектов.

Это сообщение отредактировал(а) mr.Anderson - 8.4.2010, 20:24


--------------------
user posted image

user posted image
PM MAIL ICQ Skype   Вверх
PashaPash
Дата 9.4.2010, 12:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 9
Всего: 49



Цитата(mr.Anderson @  8.4.2010,  20:17 Найти цитируемый пост)
А есть ли способ метод загрузки данных свойств вынести в отдельный статический класс? Ведь внутри каждого класса этот метод будет повторяться и иметь одну и ту же реализацию... Это ведь не очень хорошо, допустим, у меня четыре класса должны загружать свои свойства, что, в каждом из них это прописывать? Впрочем, тут хоть с моим вариантом, хоть с вашим, все равно проблемы получаются.

Не обязательно повторять метод в каждом классе. Вообще сам метод нужен только для:
- передачи типа в XmlSerializer
- приведения типа при возврате объекта.

XmlSerializer появился до 2-го дотнета и до генериков. Сейчас достаточно написать обертку типа:
Код

        public static T Load<T>(string path)
        {
            using (var stream = File.OpenRead(path))
            {
                XmlSerializer serializer = new XmlSerializer(typeof(T));
                return (T)serializer.Deserialize(stream);
            }
        }

И загружать хоть рыбу, хоть карты:
Код

        Fish fish = Load<Fish>("fish.xml");
        Map map = Load<Map>("map.xml");



--------------------
PM MAIL WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Прежде чем создать тему, посмотрите сюда:
Partizan
PashaPash

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов.
Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :)
Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, Partizan, PashaPash.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | .NET для новичков | Следующая тема »


 




[ Время генерации скрипта: 0.0634 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.