有没有办法只在 C# 中设置一次属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/839788/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 03:37:43  来源:igfitidea点击:

Is there a way of setting a property once only in C#

c#.net

提问by Nick Randell

I'm looking for a way to allow a property in a C# object to be set once only. It's easy to write the code to do this, but I would rather use a standard mechanism if one exists.

我正在寻找一种方法,允许只设置一次 C# 对象中的属性。编写代码来执行此操作很容易,但如果存在标准机制,我宁愿使用标准机制。

public OneShot<int> SetOnceProperty { get; set; }

What I want to happen is that the property can be set if it is not already set, but throw an exception if it has been set before. It should function like a Nullable value where I can check to see if it has been set or not.

我想要发生的是,如果尚未设置该属性,则可以对其进行设置,但如果之前已设置,则抛出异常。它的功能应该类似于 Nullable 值,我可以在其中检查它是否已设置。

采纳答案by Marc Gravell

There is direct support for this in the TPL in .NET 4.0;

.NET 4.0 中的 TPL 对此有直接支持;

(edit: the above sentence was written in anticipation of System.Threading.WriteOnce<T>which existed in the "preview" bits available at the time, but this seems to have evaporated before the TPL hit RTM/GA)

(编辑:上面的句子是在预期System.Threading.WriteOnce<T>当时可用的“预览”位中存在的情况下写的,但这似乎在 TPL 命中 RTM/GA 之前就消失了)

until then just do the check yourself... it isn't many lines, from what I recall...

在那之前,自己检查一下……它不是很多行,据我回忆……

something like:

就像是:

public sealed class WriteOnce<T>
{
    private T value;
    private bool hasValue;
    public override string ToString()
    {
        return hasValue ? Convert.ToString(value) : "";
    }
    public T Value
    {
        get
        {
            if (!hasValue) throw new InvalidOperationException("Value not set");
            return value;
        }
        set
        {
            if (hasValue) throw new InvalidOperationException("Value already set");
            this.value = value;
            this.hasValue = true;
        }
    }
    public T ValueOrDefault { get { return value; } }

    public static implicit operator T(WriteOnce<T> value) { return value.Value; }
}

Then use, for example:

然后使用,例如:

readonly WriteOnce<string> name = new WriteOnce<string>();
public WriteOnce<string> Name { get { return name; } }

回答by Michael Meadows

You can roll your own (see the end of the answer for a more robust implementation that is thread safe and supports default values).

您可以推出自己的(有关线程安全并支持默认值的更健壮的实现,请参见答案的末尾)。

public class SetOnce<T>
{
    private bool set;
    private T value;

    public T Value
    {
        get { return value; }
        set
        {
            if (set) throw new AlreadySetException(value);
            set = true;
            this.value = value;
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}

You can use it like so:

你可以像这样使用它:

public class Foo
{
    private readonly SetOnce<int> toBeSetOnce = new SetOnce<int>();

    public int ToBeSetOnce
    {
        get { return toBeSetOnce; }
        set { toBeSetOnce.Value = value; }
    }
}

More robust implementation below

下面更强大的实现

public class SetOnce<T>
{
    private readonly object syncLock = new object();
    private readonly bool throwIfNotSet;
    private readonly string valueName;
    private bool set;
    private T value;

    public SetOnce(string valueName)
    {
        this.valueName = valueName;
        throwIfGet = true;
    }

    public SetOnce(string valueName, T defaultValue)
    {
        this.valueName = valueName;
        value = defaultValue;
    }

    public T Value
    {
        get
        {
            lock (syncLock)
            {
                if (!set && throwIfNotSet) throw new ValueNotSetException(valueName);
                return value;
            }
        }
        set
        {
            lock (syncLock)
            {
                if (set) throw new AlreadySetException(valueName, value);
                set = true;
                this.value = value;
            }
        }
    }

    public static implicit operator T(SetOnce<T> toConvert)
    {
        return toConvert.value;
    }
}


public class NamedValueException : InvalidOperationException
{
    private readonly string valueName;

    public NamedValueException(string valueName, string messageFormat)
        : base(string.Format(messageFormat, valueName))
    {
        this.valueName = valueName;
    }

    public string ValueName
    {
        get { return valueName; }
    }
}

public class AlreadySetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has already been set.";

    public AlreadySetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

public class ValueNotSetException : NamedValueException
{
    private const string MESSAGE = "The value \"{0}\" has not yet been set.";

    public ValueNotSetException(string valueName)
        : base(valueName, MESSAGE)
    {
    }
}

回答by Vizu

No such feature in C# (as of 3.5). You have to code it yourself.

C# 中没有这样的功能(从 3.5 开始)。你必须自己编码。

回答by Anton Gogolev

This can be done with either fiddling with flag:

这可以通过摆弄标志来完成:

private OneShot<int> setOnce;
private bool setOnceSet;

public OneShot<int> SetOnce
{
    get { return setOnce; }
    set
    {
        if(setOnceSet)
            throw new InvalidOperationException();

        setOnce = value;
        setOnceSet = true;
    }
}

which is not good since you can potentially receive a run-time error. It's much better to enforce this behavior at compile-time:

这不好,因为您可能会收到运行时错误。在编译时强制执行此行为要好得多:

public class Foo
{
    private readonly OneShot<int> setOnce;        

    public OneShot<int> SetOnce
    {
        get { return setOnce; }
    }

    public Foo() :
        this(null)
    {
    }

    public Foo(OneShot<int> setOnce)
    {
        this.setOnce = setOnce;
    }
}

and then use either constructor.

然后使用任一构造函数。

回答by JaredPar

As Marc said there is no way to do this by default in .Net but adding one yourself is not too difficult.

正如 Marc 所说,在 .Net 中默认没有办法做到这一点,但自己添加一个并不太困难。

public class SetOnceValue<T> { 
  private T m_value;
  private bool m_isSet;
  public bool IsSet { get { return m_isSet; }}
  public T Value { get {
    if ( !IsSet ) {
       throw new InvalidOperationException("Value not set");
    }
    return m_value;
  }
  public T ValueOrDefault { get { return m_isSet ? m_value : default(T); }}
  public SetOnceValue() { }
  public void SetValue(T value) {
    if ( IsSet ) {
      throw new InvalidOperationException("Already set");
    }
    m_value = value;
    m_isSet = true;
  }
}

You can then use this as the backing for your particular property.

然后,您可以将其用作您特定财产的支持。

回答by ruslander

You can do this but is not a clear solution and code readability is not the best. If you are doing code design you can have a look at singletonrealization in tandem with AOP to interceptsetters. The realization is just 123 :)

您可以这样做,但不是一个明确的解决方案,并且代码可读性不是最好的。如果你正在做代码设计,你可以看看单例实现与 AOP 结合以拦截setter。实现只是 123 :)

回答by allonhadaya

interface IFoo {

    int Bar { get; }
}

class Foo : IFoo {

    public int Bar { get; set; }
}

class Program {

    public static void Main() {

        IFoo myFoo = new Foo() {
            Bar = 5 // valid
        };

        int five = myFoo.Bar; // valid

        myFoo.Bar = 6; // compilation error
    }
}

Notice that myFoo is declared as an IFoo, but instantiated as a Foo.

请注意,myFoo 被声明为 IFoo,但实例化为 Foo。

This means that Bar can be set within the initializer block, but not through a later reference to myFoo.

这意味着可以在初始化程序块中设置 Bar,但不能通过稍后对 myFoo 的引用来设置。

回答by Josh Winkler

Have you considered readonly? http://en.csharp-online.net/const,_static_and_readonly

你考虑过只读吗?http://en.csharp-online.net/const,_static_and_readonly

It's only available to set during init, but might be what you are looking for.

它只能在 init 期间设置,但可能是您正在寻找的。

回答by VoteCoffee

The answers assume that objects that receive a reference to an object in the future will not try to change it. If you want to protect against this, you need to make your write-once code only work for types that implement ICloneable or are primitives. the String type implements ICloneable for example. then you would return a clone of the data or new instance of the primitive instead of the actual data.

答案假设将来接收到对象引用的对象不会尝试更改它。如果您想防止这种情况发生,您需要使您的一次性代码仅适用于实现 ICloneable 或原语的类型。例如,String 类型实现了 ICloneable。那么您将返回数据的克隆或原始数据的新实例而不是实际数据。

Generics for primitives only: T GetObject where T: struct;

仅适用于原语的泛型:T GetObject where T: struct;

This is not needed if you know that objects that get a reference to the data will never overwrite it.

如果您知道获取数据引用的对象永远不会覆盖它,则不需要这样做。

Also, consider if the ReadOnlyCollection will work for your application. an exception is thrown whenever a change is attempted on the data.

此外,请考虑 ReadOnlyCollection 是否适用于您的应用程序。每当尝试对数据进行更改时都会引发异常。

回答by Smagin Alexey

/// <summary>
/// Wrapper for once inizialization
/// </summary>
public class WriteOnce<T>
{
    private T _value;
    private Int32 _hasValue;

    public T Value
    {
        get { return _value; }
        set
        {
            if (Interlocked.CompareExchange(ref _hasValue, 1, 0) == 0)
                _value = value;
            else
                throw new Exception(String.Format("You can't inizialize class instance {0} twice", typeof(WriteOnce<T>)));
        }
    }

    public WriteOnce(T defaultValue)
    {
        _value = defaultValue;
    }

    public static implicit operator T(WriteOnce<T> value)
    {
        return value.Value;
    }
}