C# 中的“仅输入”参数

本文关键字:仅输入 参数 输入 中的 | 更新日期: 2023-09-27 18:35:46

我最近被问到我们是否可以在方法中使用仅输入参数?我对这个问题的直接回答是"否",但当我再次思考时,我不确定我是否分享了正确的信息。此外,我没有一个合理的解释为什么 .NET 不允许我们有一个仅输入参数,就像我们在实现逆变时使用的那样。

C# 中的“仅输入”参数

您可以在此处阅读有关传递参数的信息。

我认为,当您说"仅输入"时,您指的是按值传递的参数。

在 c# 中,通常按值传递参数。当您传递对象时,您正在按值传递"指针"。因此,如果您更改指针指向的内容,则外部变量将不会更改。但是,如果您更改对象的内容,因为两个"指针"都指向同一对象,因此都可以看到更改。

因此,您可以通过"value"传递所有内容,但是如果您要传递对象,则必须创建它的副本以避免它被您调用的方法修改。

你可以在这里看到一个小的测试示例来理解我的意思。

[TestMethod]
    public void TestMethod()
    {
        var john = new Person() { Name = "John" };
        var tom = new Person() { Name = "Tom" };
        var person1 = john;
        var person2 = tom;
        SwapPersonsMethod1(person1, person2);
        //Person1 is still John
        Assert.AreEqual(person1, john);
        //Person2 is still Tom
        Assert.AreEqual(person2, tom);
        SwapPersonsMethod2(ref person1, ref person2);
        //Person1 is still Tom
        Assert.AreEqual(person1, tom);
        //Person2 is still John
        Assert.AreEqual(person2, john);
        UpdateName(person1, "Tomas");
        //Person1 is pointing to var tom, and its name now is Tomas.
        Assert.AreEqual(person1.Name, "Tomas");
        Assert.AreEqual(tom.Name, "Tomas");
        SwapPersonsMethod3(person1, person2, "Jonathan");
        //Person1 is still Tom
        Assert.AreEqual(person1, tom);
        //Person2 is still John
        Assert.AreEqual(person2, john);
        //John name has changed to Jonathan
        Assert.AreEqual(person2.Name, "Jonathan");
        Assert.AreEqual(john.Name, "Jonathan");
    }
    private void UpdateName(Person person, string name)
    {
        person.Name = name;
    }
    private void SwapPersonsMethod1(Person person1, Person person2)
    {
        var aux = person1;
        person1 = person2;
        person2 = aux;
    }
    private void SwapPersonsMethod2(ref Person person1, ref Person person2)
    {
        var aux = person1;
        person1 = person2;
        person2 = aux;
    }
    private void SwapPersonsMethod3(Person person1, Person person2, string name)
    {
        var aux = person1;
        person1 = person2;
        person2 = aux;
        UpdateName(person1, name);
    }
    public class Person
    {
        public string Name { get; set; }
    }

我在这里飞跃,但听起来你在问是否可以阻止被调用的函数更改参数的内容(如调用者所见)。

例如:确保鲍勃不喜欢编程

public void Run()
{
    Person Bob = New Person();
    Bob.LikesToProgram = false;
    Helper(Bob);
    Console.WriteLine("Bob likes to program = " + Bob.LikesToProgram);
    //Output: Turns out Bob likes to program!!!
}
public void Helper(Person input)
{
    input.LikesToProgram = true;
}  

您的问题的答案是。 。 。这取决于参数类型!

您始终可以通过模式创建不可变类型!


按值
传递在进入传递对象的引用之前,这是一个一些澄清。所有参数都是"按值"的。 创建副本并将其传递给被调用的函数。 有趣的是,如果参数不是值,而是指向(尽管引用会更准确)值。
这就把我们带到了.. .

对象
我们在那里做的是传递对对象的引用。
(正如@Servy所指出的,"按值传递引用"。
如果我们传递了一个基元(不是引用类型),帮助程序中的更改将保留在帮助程序中。


要使基元的行为类似于引用类型(这与您所询问的完全相反),您需要使用 ref 关键字。

"对象"例外
某些语言允许您显式定义不可变对象。 也就是说,有直接的编译器支持来实现不可变性。 有关示例,请参阅链接的文章。

换句话说,如果我创建一个 Person 并将实例传递给新方法,该方法将受到影响。 接收自己的 Person 副本!

为了保持有趣,.Net 有一个本机不可变类型:字符串。 字符串继承自 Object,但是当您更改字符串时,它会导致创建字符串的新实例。 您的参数现在指向对象的另一个实例。

需要明确的是,通过模式还有其他不可变的对象,但直接支持意味着你永远不需要编写

public void Run()
{
    String foo = "foo";
    Helper(foo);
}
public void Helper(String input)
{
    input = new String(Foo + "Bar");
    //Or CopyTo as seen in Arrays, etc.
}

但这能做到吗? - 是的!!


总有办法的!

  1. 将所有属性设为只读,并且只允许在实例化时设置值(通过构造函数)。想要更改值?您需要创建一个新实例!

    如果使用此策略,则对象实现 iCloneable 会很有帮助。 你问为什么是可克隆的?深拷贝与浅拷贝

  2. 假设您正在编写一个外部库,并希望您的库的使用者将您的对象视为不可变的......(即:我写了它,所以我可以做任何我想做的事。 另一方面,你不能! 只需执行与 #1 相同的操作,但使用访问修饰符比私有修饰符更宽松。 例如:内部

希望对您有所帮助!

更新:@Dzyann首先提供了类似的答案(我太啰嗦了)。 如果你喜欢我的回答,一定要给他打勾。

我认为

,你应该停止调用 c# 中没有的东西。您可以在 MSDN 上阅读有关refout关键字的信息。我只是让你知道基元类型

void MyVoid(int p1)

按值传递 - 表示它们被复制。在这里我必须进行更正:其他类型也通过 Val 传递,但作为对现有对象的引用。

如果通过引用传递所有参数,您的代码会更快但更危险,因为这样您就可以通过不复制它来节省时间。但同样,在这种情况下,您将需要有很高的编码纪律。

"对于为什么 .NET 不允许我们仅输入参数,我没有合理的解释" - 这正是 .Net 对于传递byval的基元类型有效的功能。

想象一下,如果 .net 必须复制作为参数传递的所有对象结构,它会有多低效?我想,这就是为什么MS决定将对象类型作为引用传递的原因