将方法参数设置为只读

5

我该如何创建一个具有只读对象参数的方法?

public class Person
{
    public string Name;
}

public void RunMe(Person p)
{
    p.Name = "XXXX";
}

var p = new Person();
p.Name = "YYYY";
RunMe(p);
Console.WriteLine(p.Name); // "XXXX"

上述代码导致人名为"XXXX"。我想防止RunMe方法改变人的属性。

2
你可以将它定义为结构体。 - Selman Genç
5个回答

10
你可以使用接口来限制访问。将接口作为参数传递给方法比传递具体对象更好。
public interface IReadablePerson
{
    string Name { get; }
}

public interface IWritablePerson
{
    string Name { set; }
}

public class Person : IReadablePerson, IWritablePerson
{
    public string Name { get; set; }
}

然后有一个像这样的方法:
public void RunMe(IReadablePerson p)
{
   p.Name = "XXXX"; //compile time error!!!
}

1
这确实是一个相当不错的解决方案,点赞+1。 - Jcl
1
@Jcl 这是一个很好的替代方案,根据系统的不同可能是可行的方法,但使用大量接口可能会使事情变得复杂,因此您需要判断是否值得这种努力和复杂性。 - Ian

5
这可以通过getset访问器完成,只需不定义set即可。
public String Name { get; }

这将创建一个属性,您可以像处理普通字段一样处理它。在属性下面,使用方法设置一个备份字段,您可以在 msdn 上了解更多信息。
如果您只需要设置属性一次,则将 set 定义为私有,并将参数传递到构造函数中:
public class Person
{
  public Person(String name)
  {
     this.Name = name;
  }

  public String Name { get; private set; }
}

但这样做不会让他执行 p.name="YYYY";,因为他期望能够这样做。如果我理解问题的意思,他想要一个方法(不在类中)不改变传入的原始对象,而不是在所有地方保护属性或字段。 - Jcl
他无法设置值。 - Jamaxack
如果属性设置为私有(private),他就无法从RunMe方法中进行设置。 - Jamaxack
@GrantWinney:你说得对,这有点不清楚,但我理解的和你的评论一样。 - Ian
1
@Ian,我理解了问题标题“将方法参数设置为只读”的含义,但是由于正文中只有一个方法,这会让我产生其他的想法...但是,毫无疑问,这个问题缺乏表达力,可能会被理解成很多不同的方式 :-) - Jcl

1

看起来你希望能够锁定成员。一种选择是将“set”操作与另一个成员相关联,就像这样:

public class Name
{
    public bool Locked { get; set; }

    private string name;
    public string Name
    {
        get { return this.name; }
        set
        {
            if(!this.Locked)
                this.name = val;
        }
    }
}

编辑:使锁定变为永久的替代方法。
public class Person
{
    private bool locked = false;
    public void Lock()
    {
        this.locked = true;
    }
    public bool Locked
    {
        get { return this.locked; }
    }
    // add same Name member as above
}

编辑:使用钥匙的另一种锁定方式。

private object key = null;

public bool Locked
{ get { return this.key != null; } }

public void Lock(object obj)
{
    if (this.key == null)
    {
        this.key = obj;
    }
}

public void Unlock(object obj)
{
    if (this.key == obj)
    {
        this.key = null;
    }
}

如果使用错误的密钥对象尝试解锁,您可以抛出异常。

如果这不是可取的,只需将Lock作为一个方法,并将一个私有成员变量“locked”设置为true。 - Jeremy
我们怎么才能解锁它呢? - Hamid Pourjam
我很困惑。成员要么可以解锁,要么不能,两者不能兼得。 - Jeremy
假设您创建了一个对象obj,您想要操作它,将其作为只读传递给方法,然后再次操作它。您该如何实现? - Hamid Pourjam
如果你的对象是单例模式呢? - Hamid Pourjam
显示剩余2条评论

1
第一次设置该属性后的行为如下:
private string _name;
public string Name 
{
    get { return _name; }
    set { // no setting }
}

这是不好的实践。如果这是一个公共类,您会为属性创建意外行为。任何使用程序集都无法查看get和set方法的主体,期望set方法以某种方式(设置值)运行。
这种行为应该放在单独的set方法中。这样,您可以查看它是否更改,如果需要,抛出异常,或者如果更改,则返回true。然后,您仍然可以使用属性获取值。
private bool _locked;
public string Name { get; private set;}
public boolSetName(string value)
{
    bool hasChanged = false;
    if(!_locked)
    {
        Name = value;
        _locked = true;
        hasChanged = true;
    }
    return hasChanged
}

我看不出来这如何会产生任何意外的行为。这没有意义(因为拥有一个不做任何事情的setter访问器是没有意义的),但这并不出乎意料。此外,这不会完全按照问题的标题所要求的那样做,因为他不希望在方法中更改该值(根据问题标题),但仍需要其他的 p.Name="YYYY"; 来更改名称。这将不会在任何地方更改它,并且它将不会打印任何内容(而不是在调用“RunMe”方法之前分配给它的名称)。 - Jcl
@Jcl 这对于任何类的消费者来说都是意外的行为,因为它是一个设置属性,但实际上并没有设置任何内容。当包含 person 类的程序集对于消费者不可编辑时,这一点就更加明显了。 - Jasper Saaltink
如果您想这样定义,那么任何改变任何对象状态的属性设置器(除了设置其后备字段)都将是“意外行为”...所有这些非标准的事情通常都有文档记录,以免使它们成为“意外”。是的,我知道您的意思,但我绝对不会用那种措辞来定义它。 - Jcl
我在 p.Name="YYYY" 部分犯了错误。 - Jasper Saaltink

0

C#中的类是引用类型,所以如果Person是一个class,那么没有办法做你想做的事情。

你可以将其作为值类型(通过使其成为struct)来实现,在这种情况下,将传递对象的副本,但这可能会在程序的其他点产生影响,所以如果你这样做要小心。


我认为其他的答案表明了有办法实现所期望的行为。 - mfluehr
@mfluehr 不是我理解的问题(基本上其他答案证明了这一点,要么通过更改类 - 而不是方法 - 要么传递不同类型)。我在这个评论中解释了这一点。 - Jcl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接