Monday, November 22, 2010

Методы или свойства

Проще всего применять такую концепцию: методы определяют действия, а свойства определяют данные. Желательно, чтобы при чтении свойств не происходило дополнительных действий, тем более “тяжеловесных”.

Не определяйте свойств “только для записи”. Если свойство извне класса должно только устанавливаться, то сделайте метод. Например, класс пользователей

public class User
{
  public Int64 Id {get; set;}
  public string Name {get; set;}
  public string Email {get; set;}
}

Пусть в базе данных есть еще поле IsDeleted, определяющее удалена запись или нет. В классе User эта запись нам, в общем-то, не нужна – удаленных пользователей мы просто не читаем из базы. В этом случае делать свойство смысла нет и правильный вариант – реализовать метод SetDeletedFlag(Id) или просто метод DeleteUser(Id).
Если же удаленные записи пользователей мы показываем, то класс будет такой:
public class User
{
  public Int64 Id {get; set;}
  public string Name {get; set;}
  public string Email {get; set;}
  public bool IsDeleted {get; set;}
}

Вопрос только как будет реализована логика удаления, да и вообще обновления записей пользователей. Возможно, это будет специальный класс UserDAL, работающий с такими записями и имеющий метод DeleteUser(User) или DeleteUser(Id). Но, в любом варианте, свойства всего лишь устанавливают соответствующие поля класса, но не выполняют действий.

Цитата

9 декабря 1708 года Пётр I выпустил указ о том, как надо относиться к начальству: «Подчинённый перед лицом начальствующим должен иметь вид лихой и придурковатый, дабы разумением своим не смущать начальства».

В чем разница между typeof и GetType

Разницу можно увидеть с помощью такого кода:

    class Base { }
    class Derived : Base { }
    class Program
    {
        static void Main()
        {
            ShowType( new Derived() );
        }
        static void ShowType( Base b )
        {
            Console.WriteLine(typeof(Base));
            Console.WriteLine(b.GetType());
        }
    }

Будет напечатано:

    Base
    Derived

Проверка символа

Если нужно проверить не равен ли символ каким-то конкретным значениям, то не пишите код:

       if  ((ch=='a') || (ch=='b') || (ch=='c')...)

Есть более простой и красивый путь сделать это:

       if ("abc".IndexOf(ch) > 0)

Получение имени файла из полного пути

Иногда в коде я вижу странные методы для получения имени файла из полного пути. Например так:

   string[] folders = fileUpload.FileName.Split(new char[] {'/', '\\'});
   fileName = folders[folders.Length - 1];

На самом деле все гораздо проще - можно просто написать
   Path.GetFileName(fileUpload.FileName).

Разбор URL

Для разбора URL на части использовать методы Replace, IndexOf и т.д. - не правильный подход. Например, удаление префикса:

url.Replace("http://", "") // так не правильно!

Почему? Как минимум, это не будет работать на https-протоколе.

Правильный путь - использовать класс UriBuilder:

namespace UriParse
{
      class Class1
      {
            [STAThread]
            static void Main(string[] args)
            {

                  UriBuilder parser = new UriBuilder("http://microsoft.com:80/default.aspx?id=55");
                  Console.WriteLine(parser.Host);    // microsoft.com
                  Console.WriteLine(parser.Scheme);  // http
                  Console.WriteLine(parser.Uri);     //
http://microsoft.com/default.aspx?id=55
                  Console.WriteLine(parser.Path);    // /default.aspx
                  Console.WriteLine(parser.Port);    // 80
                  Console.WriteLine(parser.Query);   // ?id=55
            }
      }
}

Saturday, November 20, 2010

Не распыляйте логику или информация в Enum

Для перечисления типов отчетов нам нужно было где-то сохранить название отчета и имя файла, хранящего его разметку.
public enum ReportTypes
{
    ComponentList = 1,
    ComponentAudit = 2,
}

Самый простой вариант – добавить специальные методы, возвращающие по типу соответствующую информацию. Внутри методов, разумеется, торчит switch:
public static string GetReportTitle(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "Список компонентов";
        case ReportTypes.ComponentAudit:
            return "Аудит компонентов";
    }

    return string.Empty;
}

public static string GetReportFileName(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "ComponentList.frx";
        case ReportTypes.ComponentAudit:
            return "ComponentAudit.frx";
    }

    return string.Empty;
}

Первая очевидная проблема в этом коде – если вдруг мы передадим неверный тип отчета, то код об этом не скажет ничего. Исправить это не сложно:
public static string GetReportTitle(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "Список компонентов";
        case ReportTypes.ComponentAudit:
            return "Аудит компонентов";
        default:
            throw new ArgumentException("ReportTypes");
    }
}

public static string GetReportFileName(ReportTypes type)
{
    switch (type)
    {
        case ReportTypes.ComponentList:
            return "ComponentList.frx";
        case ReportTypes.ComponentAudit:
            return "ComponentAudit.frx";
        default:
            throw new ArgumentException("ReportTypes");
    }
}

Стало получше, но логика раскидана в трех местах – собственно в перечислении и в двух методах.
Можно попробовать собрать информацию с помощью класса, возвращающего все поля сразу:
public class ReportInfo
{
    public string Title { get; set; }
    public string FileName { get; set; }
}

public static ReportInfo GetReportInfo(ReportTypes type)
{
...
}

Теперь логика раскидана всего в двух местах, но мне не нравится и это. При добавлении нового типа отчета в перечисление нужно вспоминать, где же лежит метод, возвращающий информацию.
В .NET если очень мощный механизм, позволяющий решить этот вопрос – атрибуты. Создаем атрибут, хранящий информацию об отчете:
    [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
    public class ReportAttribute : Attribute
    {
        public ReportAttribute(string title, string fileName)
        {
            this.ReportTitle = title;
            this.ReportFile = fileName;
        }

        public string ReportTitle
        {
            get; protected set;
        }

        public string ReportFile
        {
            get; protected set;
        }
    }

Добавляем информацию прямо в перечисление:
public enum ReportTypes
{
    [ReportAttribute("Список компонентов", "ComponentList.frx")]
    ComponentList = 1,
    [ReportAttribute("Аудит компонентов", "ComponentAudit.frx")]
    ComponentAudit = 2,
}

Делаем класс, работающий с этими атрибутами:
public static class ReportTypesHelper
{
    private static Type type = typeof(ReportTypes);

    /// <summary>
    /// Атрибут отчета для конкретного типа отчета
    /// </summary>
    public static ReportAttribute GetReportAttribute(ReportTypes reportType)
    {
        FieldInfo propInfo = type.GetField(reportType.ToString());
        if (propInfo != null)
        {
            var attribs = propInfo.GetCustomAttributes(typeof(ReportAttribute), false) as ReportAttribute[];

            if (attribs != null && attribs.Length > 0)
            {
                return attribs[0];
            }
        }

        return null;
    }

    /// <summary>
    /// Список отчетов
    /// </summary>
    /// <returns></returns>
    public static IEnumerable<ReportAttribute> GetReportList()
    {
        var obj = Enum.GetValues(type);

        foreach (ReportTypes item in obj)
        {
            yield return GetReportAttribute(item);
        }
    }
}

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