Существует ли более эффективный способ определения аналогичных общедоступных свойств
У меня есть класс с почти 20 общедоступными свойствами. Эти свойства имеют общее значение, что все они являются строками, и они заполнены данными из разных таблиц базы данных.
Кроме того, набор довольно нормальный, в то время как get является особенным, поскольку мне нужно вызвать конкретный метод. Это делается для каждого свойства в данный момент (см. Ниже).
Мой вопрос здесь: есть ли еще более эффективный способ сделать это, таким образом, когда мне не нужно определять каждое публичное свойство вручную таким образом?
class myclass
{
private string _Firstname;
private string _Lastname;
.....
public string Firstname
{
get {
return ModifyStringMethod(this._Firstname);
}
set {
this._Firstname = value;
}
}
}
Как упоминалось выше, каждое государственное имущество выглядит одинаково. Получает вызовы ModifyStringMethod с частным членом, заданным как параметр, в то время как набор просто устанавливает частный член.
Ответы
Ответ 1
Вы можете попробовать автоматическое создание кода с помощью T4 template. Они идеальны, когда у вас есть простой, повторяющийся образец кода, и вы не ожидаете, что некоторые случаи будут немного отличаться от других.
Просто определите XML со списком имен свойств и создайте шаблон T4 для частичного класса с каждым свойством.
Ответ 2
Другая опция похожа на решение Dion V., но использует неявное преобразование, чтобы заставить поведение вести себя как обычная строка извне и позволяет использовать простое автоматическое свойство. Но это работает только в том случае, если ModifyStringMethod
является статическим и не требует параметров вне класса.
public struct EllipsisString
{
private string _value;
public string Value {get { return _value; }}
public EllipsisString(string value)
{
_value = value;
}
// implicit conversion from string so it is possible to just assign string to the property
public static implicit operator EllipsisString(string value)
{
return new EllipsisString(value);
}
public static implicit operator string(EllipsisString original)
{
return SpecialMethod(original.Value);
}
public override string ToString()
{
return SpecialMethod(Value);
}
private static string SpecialMethod(string value)
{
return value + "...";
}
}
И использование прост:
private EllipsisString FirstName { get; set; }
public void Method()
{
FirstName = "Thomas";
Console.WriteLine(FirstName);
Console.WriteLine(FirstName.Value);
}
Ответ 3
PostSharp - еще одна альтернатива.
Вы просто применяете атрибут над классом и записываете свои свойства с помощью "get; set;" синтаксис.
PostSharp - это инструмент .NET, который позволяет разработчикам применять аспекты кода, который должен быть выполнен, там, где есть сборки, пространства имен, классы или методы.
В частности, PostSharp позволяет разработчикам писать меньше кода, применяя атрибуты к блокам кода, которые позже будут иметь код, отражающий этот аспект, который будет использоваться и исполнен с выбранным блоком кода. Этот подход значительно сокращает "сантехнику", которая избыточна в базе кода.
Общие варианты использования включают следующее:
-
Вход
-
Безопасность
-
Отмена/Повтор
-
INotifyPropertyChanged
-
ExceptionHandling
Ответ 4
Когда я сталкивался с этим в прошлом, я использовал специальные фрагменты кода в VS, чтобы легко создавать свойства. См. Ссылку ЗДЕСЬ.
Затем, когда вам нужно добавить новое свойство, вы просто вызываете фрагмент кода и при необходимости заполняете имена элементов.
Хотя это не обязательно отрицает необходимость иметь много похожих свойств,
это упрощает их создание (в том же порядке, что и использование шаблонов T4, как указано выше).
Ответ 5
Если вы можете сделать свои свойства виртуальными, вы можете использовать перехватчик с помощью динамического прокси-сервера Castle.
Перехватчик содержит поведение, которое может выполняться при вызове данного метода. В этом случае мы ModifyStringMethod возвращаем значение свойства string.
Как
1) Добавить ссылку на пакет nuget Castle.Core
2) Определите свой перехватчик
public class ModifyStringMethodInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
if (invocation.Method.Name.StartsWith("get_") &&
invocation.Method.ReturnType == typeof(string))
{
invocation.ReturnValue =
ModifyStringMethod((string)invocation.ReturnValue);
}
}
private static string ModifyStringMethod(string input)
{
return (input ?? "") + "MODIFIED";
}
}
В приведенном выше примере есть метод Intercept, который будет вызываться при вызове вашего свойства. Вы можете увидеть в примере invocation.Proceed(), это продолжает вызов свойства.
Затем он проверяет, является ли его свойство get_ и возвращает строку
if (invocation.Method.Name.StartsWith("get_") &&
invocation.Method.ReturnType == typeof(string))
Затем изменяет возвращаемое значение метода.
invocation.ReturnValue = ModifyStringMethod((string)invocation.ReturnValue);
3) Определите свои объекты, к которым вы хотите добавить это поведение, с помощью виртуального метода (обратите внимание, что я также могу использовать автоматически реализованные свойства) BONUS
public class Intercepted
{
public virtual string A { get; set; }
}
4) Затем создайте экземпляры объекта, используя класс ProxyGenerator в DynamicProxy,
например
public class Program
{
static void Main(string[] args)
{
var pg = new ProxyGenerator();
// Intercepted will be an instance of Intercepted class with the
// ModifyStringMethodInterceptor applied to it
var intercepted = pg.CreateClassProxy<Intercepted>(new ModifyStringMethodInterceptor());
intercepted.A = "Set ... ";
Console.WriteLine(intercepted.A);
Console.ReadLine();
}
}
Выход
Set ... MODIFIED
Преимущество здесь в том, что ваши объекты "чисты", например, им не нужно знать о ModifyStringMethodInterceptor и может содержать автоматически реализованные свойства, которые, если у вас много таких объектов, уменьшат количество кода на большие суммы.
Переход на один шаг вперед, если вам нужно дальнейшее управление, вы можете применить это поведение, добавив в класс атрибут, например
[AttributeUsage(AttributeTargets.Method)]
public class ModifyStringMethodAttribute : Attribute
{
}
Затем объекты определяются как:
public class Intercepted
{
public virtual string A { [ModifyStringMethod] get; set; }
}
И переход к перехватчику:
if (invocation.Method.ReturnType == typeof(string) &&
invocation.Method.GetCustomAttributes(true)
.OfType<ModifyStringMethodAttribute>().Any())
{
invocation.ReturnValue =
ModifyStringMethod((string)invocation.ReturnValue);
}
Проверить атрибут и затем применить вызов метода.
Ответ 6
Однако я лично не поклонник этого решения, вы можете сделать что-то вроде этого:
class MyClass
{
private IDictionary<string, string> propertyValueByName = new Dictionary<string, string>();
public string this[string propertyName]
{
get { return propertyValueByName[propertyName]; }
set { propertyValueByName[propertyName] = ModifyStringMethod(value); }
}
public string FirstName
{
get { return this["FirstName"]; }
set { this["FirstName"] = value; }
}
public string LastName
{
get { return this["LastName"]; }
set { this["LastName"] = value; }
}
}
Ответ 7
Пример с использованием отражения
class MyClass
{
public string FirstName { private get; set; }
public string LastName { private get; set; }
public string GetModifiedValue(string propertyName)
{
var prop = this.GetType().GetProperty(propertyName);
return ModifyStringMethod((string)prop.GetValue(this, null));
}
}
Итак, чтобы получить каждое измененное значение вместо использования MyClass.FirstName
, вы должны использовать MyClass.GetModifiedValue("FirstName")
Ответ 8
Вы можете создать собственный фрагмент кода, вот пример обрезанного, который я создал для себя, чтобы автоматизировать создание свойств с уведомлением об изменении, вы можете использовать это как шаблон:
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>propnot</Title>
<Shortcut>propnot</Shortcut>
<Description>Code snippet for property and backing field with property change event</Description>
<Author>Radin Gospodinov</Author>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>Property type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>Property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
<Literal>
<ID>field</ID>
<ToolTip>The variable backing this property</ToolTip>
<Default>myVar</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[private $type$ $field$;
public $type$ $property$
{
get { return this.$field$;}
set {
if(this.$field$ != value) {
$field$ = value;
this.RaisePropertyChanged(() => this.$property$);
}
}
}
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
Ответ 9
Если вы действительно хотите пойти с этим подходом, то генерация кода, использующая что-то вроде шаблонов T4, или CodeSmith, вероятно, способ пойти, однако я согласен с @DanBryant, что создание ваших свойств, подобных этому, может привести к тому, что счетчик будет интуитивно понятным класс. Я бы ожидал, что такой код будет работать:
X.FirstName = "Some random long name";
Assert.AreEqual("Some random long name", X.FirstName);
Из вашего комментария, с вашим дизайном класса это может не сработать (в зависимости от длины усечения в вашем ModifyStringMethod
вы действительно можете получить X.FireName == "Some rand..."
. Это кажется неправильным.
Лучшим подходом может быть реализация модификации вне поведения свойства, возможно, в методе расширения. Так что-то вроде этого:
public static class FormatStringExtensions {
public static string ModifyStringForOutput(this string me) {
if (me.Length > 10) {
return me.Substring(0, 10) + "...";
}
return me;
}
};
Позволит вам определить ваши классы данных с использованием автоматических свойств:
public class myclass {
public string FirstName { get; set; }
public string LastName {get; set; }
};
И затем измените значение строки из свойств как и когда оно будет использоваться с использованием метода расширения:
var instance = new myclass();
instance.FirstName = "01234567890123";
Console.WriteLine("Original Name {0}\nModified Name {1}\n",
instance.FirstName,
instance.FirstName.ModifyStringForOutput());
Это позволяет вашим свойствам продолжать работать как обычные свойства, ожидая, предоставляя вам простой способ доступа к форматированным строкам, если это необходимо.
Ответ 10
Альтернативой является создание простого фрагмента, использующего имя типа и свойства как переменную. Это намного быстрее, чтобы сгенерировать класс, и вы полностью контролируете свой код.
Ответ 11
Вы можете определить свой собственный класс для наследования из DynamicObject
public class MyExpando : DynamicObject
{
Dictionary<string, object> dictionary = new Dictionary<string, object>();
//Want to create properties on initialization? Do it in the constructor
public MyExpando()
{
dictionary.Add("PreferredName", "Darth Sidious");
dictionary.Add("GreatDialog", "Something, something, darkside!");
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
bool success = dictionary.TryGetValue(binder.Name, out result);
if (success)
result = ModifiedValue(result);
return success;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
dictionary[binder.Name] = value;
return true;
}
private string ModifiedValue(object val)
{
//Modify your string here.
if (val.ToString() != "Darth Sidious")
return "Something something complete";
return val.ToString();
}
}
Хотите создать свойство не в конструкторе? Тогда вы можете просто сделать
dynamic x = new MyExpando();
x.FirstName = "Sheev";
x.LastName = "Palpatine"
Console.WriteLine(x.PreferredName + " says : \"" + x.GreatDialog + "\"");
Console.ReadKey();
Великая вещь в том, что вы можете реализовать INotifyPropertyChanged, а затем вызывать его в вашем методе TrySetMember
, вы также можете ограничить доступ к вашему "Настройщику" свойств или getter, просто выбросив исключение, основанное на имени свойства в TryGetMember или TrySetMember, и просто измените модификатор доступа словаря для классов, которые наследуют от MyExpando
, чтобы имитировать наследование свойств.
Пример того, как ограничить доступ к устройству свойств.
public override bool TrySetMember(SetMemberBinder binder, object value)
{
if (!dictionary.ContainsKey(binder.Name))
return false;
dictionary[binder.Name] = value;
return true;
}