Строковый объект с фиксированной длиной С#

У меня есть класс, в котором я хочу использовать строки с фиксированным размером. Причиной фиксированного размера является то, что класс "сериализуется" в текстовый файл со значениями с фиксированной длиной. Я хочу избежать, чтобы написать foreach значение в качестве предложения охраны и вместо этого обработать класс.

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

    public String CompanyNumber
    {
        get 
        {
            return m_CompanyNumber.PadLeft(5, ' ');
        }
        set 
        {
            if (value.Length > 5) 
            {
                throw new StringToLongException("The CompanyNumber may only have 5 characters", "CompanyNumber");
            }
            m_CompanyNumber = value; 
        }
    } 

Я хотел бы иметь String, который обрабатывает это сам по себе. В настоящее время у меня есть следующее:

public class FixedString
{
    String m_FixedString;

    public FixedString(String value)
    {
        if (value.Length > 5) 
        {
            throw new StringToLongException("The FixedString value may consist of 5 characters", "value");
        }
        m_FixedString= value;
    }

    public static implicit operator FixedString(String value)
    {
        FixedString fsv = new FixedString(value);
        return fsv;
    }

    public override string ToString()
    {
         return m_FixedString.PadLeft(5,' ');
    }
}

Проблема с этим решением заключается в том, что я не могу установить длину строки во время компиляции.

Было бы идеально, если бы он выглядел как-то в конце

public FixedString<5> CompanyNumber { get; set; }

Ответы

Ответ 1

Сделать FixedString значение размера в качестве параметра конструктора, но не самого значения

public class FixedString
{
   private string value;
   private int length;
   public FixedString(int length)
   {
      this.length = length;
   }

   public string Value 
   {
       get{ return value; }
       set
       {
            if (value.Length > length) 
            {
                 throw new StringToLongException("The field may only have " + length + " characters");
            }
           this.value = value; 
       }
   }
}

Начните его с вашего класса и просто установите Value, когда он изменит

public class MyClass
{
    private FixedString companyNumber = new FixedString(5);

    public string CompanyNumber
    {
        get{ return companyNumber.Value; }
        set{ companyNumber.Value = value; }
    }
}

Ответ 2

Я бы пошел дальше и задал вопрос о дизайне. Это решение объединяет две проблемы: внутреннее состояние приложения и формат хранения, которые должны оставаться отдельными.

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

Ответ 3

Вы можете определить Interface следующим образом:

public interface ILength
{
    int Value { get; }
}

Некоторая структура, реализующая интерфейс:

public struct LengthOf5 : ILength
{
    public int Value { get { return 5; } }
}

public struct LengthOf10 : ILength
{
    public int Value { get { return 10; } }
}

И затем:

public class FixedString<T> where T : struct, ILength
{
    String m_FixedString;

    public FixedString(String value)
    {
        if (value.Length > default(T).Value)
        {
            throw new ArgumentException("The FixedString value may consist of " + default(T).Value + " characters", "value");
        }
        m_FixedString = value;
    }

    public static implicit operator FixedString<T>(String value)
    {
        FixedString<T> fsv = new FixedString<T>(value);
        return fsv;
    }

    public override string ToString()
    {
        return m_FixedString;
    }
}

Честно говоря, я не знаю, нравится ли мне это решение, но это лучшее, что я могу решить, чтобы решить вашу проблему.

Ответ 4

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

using System.ComponentModel.DataAnnotations;

public class MyObject
{
   [StringLength(5)]
   public String CompanyName { get; set; }
}

public void Save(MyObject myObject)
{
   List<ValidationResult> results = new List<ValidationResult>();
   ValidationContext context = new ValidationContext(myObject, null, null);
   bool isValid = Validator.TryValidateObject(myObject, context, results);

   if (!isValid)
   {
      foreach (ValidationResult result in results)
      {
         // Do something
      }
   }
}

Подробнее о DataAnnotations здесь.

Ответ 5

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

К сожалению, что-то вроде определения типа, которое вы предложили (FixedString<5>), невозможно в контексте .NET.

Некоторые из ответов до сих пор говорили об обходных решениях, альтернативах или других идеях, я хотел бы вместо этого ответить, почему вы не можете делать то, что вы изначально запросили на С#.

Прежде всего, давайте посмотрим, как вы можете сделать это на произвольном языке:

Шаблоны:. Вы можете сделать что-то подобное на С++, используя систему шаблонов. Как пишет Эрик Липперт в своей статье о различиях между дженериками и шаблонами, "вы можете думать о шаблонах как механизм поиска и заметок для причудливых штанов" (https://blogs.msdn.microsoft.com/ericlippert/2009/07/30/whats-the-difference-part-one-generics-are-not-templates/).

.NET-дженерики во многих отношениях намного проще сравнивать. Общие типы позволяют вам параметризовать типы, но не над значениями, а открытые типы разрешаются во время выполнения, тогда как шаблоны являются полностью компилируемой конструкцией времени.

Зависимые типы: Несколько языков поддерживают функцию, называемую зависимыми типами (https://en.wikipedia.org/wiki/Dependent_type). Это позволяет вам определять типы, зависящие от значений. Многие языки, которые поддерживают эту функцию, ориентированы на доказательство теоремы, а не на развитие общего назначения.

Идрис, пожалуй, необычен в том, что он является языком общего назначения при активной разработке (хотя и малоизвестной), которая поддерживает эту функцию (см. http://www.idris-lang.org/).

С#

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

Я думаю, что здесь есть много хороших предложений о том, как вы можете реализовать что-то подобное на С#, но все они сводятся к проверке времени выполнения.