是否有更有效的方法来定义类似的公共属性

时间:2015-04-01 12:26:42

标签: c#

我有一个有近20个公共财产的班级。这些属性的共同之处在于它们都是字符串,并且它们填充了来自数据库的不同表的数据。

此外,该设置非常正常,而get是特殊的,因为我需要调用特定的方法。目前为每个房产做了这件事(见下文)。

我的问题是:是否有另一种更有效的方法,这样我就不必用这种方式手工定义每个公共财产?

class myclass
{
     private string _Firstname;
     private string _Lastname;
     .....
     public string Firstname
     {
         get {
             return ModifyStringMethod(this._Firstname);
         }

         set {
             this._Firstname = value;
         }
     }
}

如上所述,每个公共财产看起来都一样。 get调用ModifyStringMethod,私有成员作为参数给出,而set只设置私有成员。

11 个答案:

答案 0 :(得分:18)

您可以尝试使用T4 template自动生成代码。当你拥有简单,重复的代码模式时,它们是完美的,你并不期望某些情况与其他情况略有不同。

只需使用属性名称列表定义XML,并让T4模板为每个属性生成部分类。

答案 1 :(得分:12)

另一个选项类似于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);
    }

答案 2 :(得分:6)

PostSharp是另一种选择。

您只需在类上应用一个属性,并使用“get; set;”编写属性。语法。

PostSharp是一个.NET工具,它使开发人员能够将要执行的代码的各个方面应用于组件,命名空间,类或方法。

具体来说,PostSharp允许开发人员通过将属性应用于代码块来编写更少的代码,这些代码块稍后将反映该方面的代码被选中,并在所选代码块中执行。这种方法显着减少了代码库中冗余的“管道”。

常见用例包括以下内容:

  • 登录

  • 安全

  • 撤消/重做

  • INotifyPropertyChanged的

  • ExceptionHandling

答案 3 :(得分:4)

在我过去遇到过这种情况时,我在VS中使用了自定义代码段来轻松创建属性。请参阅链接HERE

然后,当您必须添加新属性时,只需调用代码段并根据需要填写项目的名称。

虽然这并不一定否定需要许多类似的属性, 它确实使它们的创建更容易(与使用上面提到的T4模板一样)。

答案 4 :(得分:4)

如果您可以将属性设置为虚拟,则可以使用带有Castle Dynamic Proxy的拦截器。

拦截器包含在调用给定方法时可以执行的行为。在这种情况下,我们将ModifyStringMethod转换为字符串属性的返回值。

如何

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)然后使用DynamicProxy中的ProxyGenerator类

创建对象实例

e.g

 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

这里的好处是你的对象是&#34;清洁&#34;例如,他们不需要知道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);
        }

检查属性,然后应用方法调用。

答案 5 :(得分:3)

但是,我个人不是这个解决方案的粉丝,你可以这样做:

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; }
    }
}

答案 6 :(得分:3)

使用反射

的示例
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")

答案 7 :(得分:3)

您可以在此处创建自定义代码段,这是我为自己创建的剪辑示例,可以使用更改通知自动创建属性,您可以将其用作模板:

<?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>

答案 8 :(得分:3)

如果你真的想要采用这种方法,那么使用像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());

这使您的属性可以像预期的那样继续工作,同时如果需要,可以轻松访问格式化的字符串。

答案 9 :(得分:2)

替代方法是使用类型和属性名称作为变量创建一个简单的代码段。生成课程要快得多,并且您可以完全控制代码。

答案 10 :(得分:2)

您可以定义要从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方法中调用它,你也可以限制访问你的&#34;属性&#34; setter或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;
    }