【问题标题】:C# Variable Getters / SettersC# 变量获取器/设置器
【发布时间】:2013-02-07 13:02:42
【问题描述】:

我正在尝试为外部程序在我的班级中获取/设置变量创建一种简单的方法。我所拥有的是一个具有多个变量的类,例如:

class myClass
{
    public int one
    {
        get{ /* get code here */ }
        set{ /* set code here */ }
    }
}

而不仅仅是变量“一个”,我有接近 100 个变量。所有设置都使用相同的方式获取和设置代码。我想要的是一种获取和设置变量的简单方法。示例:不必这样做:

myClass c = new myClass();
c.one = 5;

我想找到一种方法来做类似的事情:

myClass c = new myClass();
c.setVariable("variableName", value);

最好让“variableName”文本来自枚举列表,这样它们就可以像这样被引用:

c.setVariable(enumName.varName, value);

我不确定该怎么做,或者是否有可能。正如我所说,我有近 100 个变量需要自己的 get/set 代码,但出于各种原因,我更喜欢只有一个公共 get 函数和一个公共 set 函数。

由于我认为反射效率不高,因此我想尽可能避免它。

我在 C# 中看到过其他类似这样的代码:

this["variableName"] = value;

但是我似乎也无法找到一种方法来完成这项工作......

【问题讨论】:

  • 在这种情况下你无法避免反思。
  • 正如 Fuex 所说,在这种情况下反射是必须的!
  • 改用Dictionary
  • 在什么方面 c.SetVariable(myClassEnum.one, 5) 比 c.one = 5 更好? - 我不明白。
  • 让你的类成为 IDictionary 的实现,你可以做你想做的事。它不会给你属性,但也许这不是你需要的

标签: c#


【解决方案1】:
this["variableName"] = value;

您描述的语法仅对字典有效。如果你不想使用反射并且只想要一个 getter 和 setter,你应该使用字典。 编辑:你知道lambda expressions吗?它们是内联匿名方法,通过将它们放在另一个字典中,您可以进行验证!像这样的:

public class MyClass
{
    private readonly Dictionary<String, Object> dictionary;
    private readonly Dictionary<String, Func<Object, bool>> validators;

    public MyClass()
    {
        this.dictionary = new Dictionary<String, Object>();
        this.validators = new Dictionary<String, Func<Object, bool>>();

        // Put the default values in the dictionary.
        this.dictionary["One"] = 10;
        this.dictionary["Another"] = "ABC";

        // The validation functions:
        this.validators["One"] = obj => ((int)obj) >= 0;
        this.validators["Another"] = obj => obj != null;
    }

    public Object this[string name]
    {
        get { return this.dictionary[name]; }
        set
        {
            // This assumes the dictionary contains a default for _all_ names.
            if (!this.dictionary.Contains(name))
                throw new ArgumentException("Name is invalid.");

            // Get a validator function. If there is one, it must return true.
            Func<Object, bool> validator;
            if (validators.TryGetValue(name, out validator) &&
                !validator(value))
                throw new ArgumentException("Value is invalid.");

            // Now set the value.
            this.dictionary[name] = value;
        }
    }
}

关于这段代码的一些注意事项:

  • 字典是一个通用对象:字符串和对象的字典,其中String 是键的类型,Object 是值的类型。如果您只有一种值(例如整数),那么您应该将所有位置的Object 更改为例如int
  • 给定一个实例MyClass myClass,你可以通过myClass["One"] = 20来设置One的值。
  • 如果您(不经意间)尝试使用不存在的名称 (var value = myClass["DoesNotExist"]) 获取值,则会出现异常。您可以将其更改为返回默认值或完全执行其他操作。
  • 要使用enum,请创建枚举并指定其类型,而不是代码中的String

【讨论】:

  • 我喜欢这个解决方案,但是我发现了一个问题。我需要不同的代码来设置/获取每个变量。一些变量有不同的有效范围,如果该值将被设置为无效的值,则需要抛出错误。
  • 那么你需要为每个变量显式地创建属性!如果它们有一些共同的因素,您可以将它们分组,但您最终会编写如此多的“简化代码”,以至于您可能一开始就添加属性。
  • @JasonLarke 我知道我需要为每个属性创建属性,但是 C# 不会让您使用相同的键创建诸如上面的索引属性。因此,我必须更改每个属性的键,而不是使用多个 [string name]。
  • @Alan:我在字典中使用了 lambda 函数来验证值。当您不需要对特定名称进行验证时,您可以从 lambda 函数返回 true 或直接省略它。
  • @Virtlink 感谢您的编辑!我没有考虑过 lambda 函数对此的用处。看起来不错。
【解决方案2】:

另一种解决方案可以通过这种方式使用反射:

public object this[string name] {
    get { return this.GetType().GetProperty(name).GetValue(this, null); }
    set { this.GetType().GetProperty(name).SetValue(this, value, null); }
}

或:

public void SetVariable(string name, object value){
    this.GetType().GetProperty(name).SetValue(this, value, null); 
}

【讨论】:

  • 我喜欢这个解决方案,不使用的原因是什么?
  • 这只是反思。你提到你不想要它。
  • 虽然我指定我不想使用反射,但这并不是完全不可能的,我喜欢这个解决方案的简单性。
  • @Turbot 嗨我想知道 set 的值是否是一个函数(假设你想要 myObject[MyClass.Variables.one] = () => {doSomethingElse()},那么我该怎么做激活那个函数,因为 value 是一个对象类型?
【解决方案3】:

我不会对设计发表评论,但它的实现相当简单。你根本不需要反思,你只需要大量的打字。一般来说,你想要的基本上是这样的:

public class MyClass
{
    public enum Variables {
        one,
        two
    }

    private int one {
        get { /* get; */ }
        set { /* set; */ }
    }

    private int two {
        get { /* get; */ }
        set { /* set; */ }
    }

    public object getVariable(Variables v) {
        switch(v) {
            case Variables.one: return one;
            case Variables.two: return two;

            default: throw new InvalidArgumentException("v");
        }
    }

    public void setVariable(Variables v, object value) {
        switch(v) {
            case Variables.one: one = (int)value; return;
            case Variables.two: two = (int)value; return;

            default: throw new InvalidArgumentException("v");
        }
    }
}

因为,你没有类型检查,所以很多事情都会在你的脸上爆炸。另一种使用反射而不引起太多性能压力的方法是静态编译 enum->getter 和 enum->setter 方法的字典(不仅仅是 MethodInfo,而是一个委托)。这将导致启动性能下降。但在获取和设置值时,它不会(afaik)损害性能(尤其是)。

[编辑]
以上也可以这样实现:

public object this[Variables v]
{
    get { return getVariable(v); /* or move implementation here */ }
    set { serVariable(v, value); /* or move implementation here */ }
}

这将使您能够编写(例如)myObject[MyClass.Variables.one] = 5

[编辑 2]
following 已经过测试工作。当面对正常反射(或巨大的开关)时,我不知道性能。

【讨论】:

  • 我仍然有点担心使用巨型开关会影响性能。这是我如何解决问题的最初想法,但是我的程序可能每秒设置变量数次(可能接近 50 次)。对此有何看法?
  • 开关应该非常快(据我所知)。它们编译成跳转表,并且比 if..elseif..elseif........else 快得多(除了你知道哪个最有可能出现的极端情况)。
  • @Alex 嗨,我想知道 set 的值是否是一个函数(假设你想要 myObject[MyClass.Variables.one] = () => {doSomethingElse()},那么我该怎么做激活那个函数,因为 value 是一个对象类型?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-27
  • 2014-10-16
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多