【问题标题】:Pointer alternative in C#C#中的指针替代
【发布时间】:2020-08-26 08:11:38
【问题描述】:

我需要一个替代 C# 中类似 C/C++ 的指针,以便我可以存储在构造函数中传递的变量的引用。我希望我的本地指针在每次引用值更改时更改其值。就像一个指针。但我不想在 C# 中使用真正的指针,因为它们不安全。有解决办法吗?

    class A
    {
        public Int32 X = 10;
    }

    class B
    {
        public B(Int32 x)
        {
            Y = x;
        }

        public Int32 Y { get; set; }
    }
    static void Main(string[] args)
    {
        A a = new A();
        B b = new B(a.X);

        Console.WriteLine(b.Y); // 10
        a.X = 11;
        Console.WriteLine(b.Y); // 10, but I want it to be 11
    }

【问题讨论】:

  • 你可以通过传递a而不是int的引用来解决这个问题,然后你可以做public int Y => a.X
  • x 值按值传递给 ctor。您可以在 B 的 ctor 中使用 ref 关键字,让您的 ctor 参数通过引用而不是值传递。在任何情况下,如果您要传递 Bitmap 类型的参数,我认为现在的程序没有问题,因为 Bitmap 是一个引用类型,所以你已经有了你的参考(和指针)。
  • 所有不是从ValueType继承的都是指针
  • @HimBromBeere 完全同意您对第一次更正的看法 - 迂腐点,不过:“但是它是通过引用传递的” - 不,值 一个引用,通过价值
  • 我需要...但我不想要一旦你学会了 C# 基础知识,这些被误导的情绪和想法就会消失。

标签: c# .net pointers


【解决方案1】:

忘记指针,开始用 C# 思考,同时用 C# 编码:D

我会这样做:

public interface IXProvider
{
     int X {get;}
}

class A : IXProvider
{
    public int X {get; set;} = 10;
}

class B
{
    public B(IXProvider x)
    {
        _x = x;
    }

    private readonly IXProvider _x;
    public int Y => _x.X;
}

static void Main(string[] args)
{
    A a = new A();
    B b = new B(a);

    Console.WriteLine(b.Y); // 10
    a.X = 11;
    Console.WriteLine(b.Y); // 11
}

位图示例:(为简单起见,假设“SomeBitmap”和“AnotherBitmap”是实际的位图)

public interface IBitmapProvider
{
     Bitmap X {get;}
}

class A : IBitmapProvider
{
    public Bitmap X {get; set;} = SomeBitmap;
}

class B
{
    public B(IBitmapProvider x)
    {
        _x = x;
    }

    private readonly IBitmapProvider _x;
    public Bitmap Y => _x.X;
}

static void Main(string[] args)
{
    A a = new A();
    B b = new B(a);

    Console.WriteLine(b.Y); // SomeBitmap
    a.X = AnotherBitmap;
    Console.WriteLine(b.Y); // AnotherBitmap
}

【讨论】:

    【解决方案2】:

    .NET 中基本上有 3 种类型的引用

    1. 对象引用(即 A 类型的本地/参数/字段,其中 A 是引用类型 - classinterfacedelegate 等)
    2. 非托管指针(即Foo* 类型的本地/参数/字段)
    3. 托管指针(即ref Foo 类型的本地/参数)

    您(非常明智地)说您不想使用“2”,而“3”只能在堆栈上使用(作为局部变量或参数),因此留下“ 1",这是可能的。例如,通过传入 A 对象实例而不是 int。请参阅@Fildor 的答案以了解此演练。

    【讨论】:

      【解决方案3】:

      更好的方法是将值类型包装到引用类型中。考虑一下:

      object o1 = new object();
      object o2 = o1;
      

      现在你可以像这样使用它了:

      class Obj {
          public int Val;
      }
      
      void Main() {
          Obj o1 = new Obj();
          o1.Val = 5;
          Obj o2 = o1;
          o2.Val = 8;
      //o1.Val is 8 now
      }
      

      【讨论】:

      • 第一个例子(o1/o2)在这种情况下要显示的内容对我来说真的不是很明显
      【解决方案4】:

      int 等值类型与MyClass 等参考类型之间存在巨大差异。由于您实际上对 引用类型的 BitMap 感兴趣,因此您不需要执行任何指针逻辑。该变量已经一个引用。改变它的状态,例如无论如何修改它的任何公共属性都会反映在所有其他引用中。

      因此,当您的类 A 有一个类型为 Bitmap 的变量时,您也可以在任何其他代码中使用该变量(假设您可以访问该成员,例如当它是 @987654326 时@你肯定不能)。

      A a = new A();
      B b = new B(a.Image); // this will just reference the bitmap instead of copying it
      b.Image.Modify(); // this will also be reflected in a.x
      

      【讨论】:

        【解决方案5】:

        我对 C# 不太熟悉,所以我无法提供有效的答案,但 C# 中肯定存在指针。

        int* ptr = &a

        查看文档:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/unsafe-code-pointers/pointer-types

        【讨论】:

        • 你需要为此使用 unsafe。
        • 是的,但是在 C# 中,您需要将其声明为“不安全”,我想绕过它
        • 是的,这确实存在,但是在使用unsafe 代码时,您需要非常非常小心。在这种情况下,OP 想要将其存储为一个字段。如果您正在谈论的是“内部指针”(即您需要使用fixed 来获取的东西),那将是灾难的根源,因为该值应该被认为是死的 fixed 块结束,因为此时允许 GC 移动原始对象(并且您的非托管指针不会得到更新)。推论:引导人们在没有警告的情况下使用高级且细致入微的语言功能:可能没有帮助
        【解决方案6】:

        C# 区分值类型引用类型。值类型(结构和原始类型,string1 除外)按值传递(复制),而引用类型(类)本质上是花哨的指针。 (顺便说一句,在 IDE 中,如果您将鼠标悬停在变量上,它会告诉您它是类还是结构)。

        因此,请记住这一点,这就是您的代码的作用:

        class A       // reference type
        {
            public Int32 X = 10;     // with a value type field
        }
        
        class B       // reference type
        {
            // The ctor takes in a value type parameter; a copy of the 
            // value is passed in (pretty much like in C++)
            public B(Int32 x)
            {
                Y = x;       // this now contains its own copy
            }
        
            // value type PROPERTY -- see below
            public Int32 Y { get; set; }
        }
        

        所以,在你的主要方法中,当你这样做时

        A a = new A();
        B b = new B(a.X);
        

        b.Y 与 a.X 没有连接,因为只要 a.X 被计算,构造函数就会得到 a.X 的副本。

        您可以做的最简单的事情是让 B 的构造函数接受 A 类型的参数,然后在内部存储对 A 的引用:

        class B
        {
            private A _a;
            
            public B(A a)    // a is passed in by reference
            {
                _a = a;       
            }
        
            public Int32 Y { 
                get => _a.X; 
                set => _a.X = value; 
            }     
        }
        

        当你这样做时

        A a = new A();
        B b = new B(a);
        

        a 指代b 内部和外部的同一个对象。更改a.X 会影响调用b.Y 的结果。

        你也可以直接暴露a

        class B
        {
            public B(A a)
            {
                A = a;
            }
        
            public A A { get; set; }
        }
        

        然后在main()中:

        A a = new A();
        B b = new B(a);
        
        Console.WriteLine(b.A.X);   // 10
        a.X = 11;
        Console.WriteLine(b.A.X);   // 11
        

        关于属性的说明

        您应该注意字段和属性之间的区别。字段是数据成员,就像在 C++ 中一样。 property 只是编译器在后台生成的 get/set 方法对的一些语法糖。大致相当于这样:

        class B
        {
            private A _a;
            
            public B(A a)
            {
                _a = a;       
            }
        
            public A GetA() { 
                return _a; 
            }     
        
            public void SetA(A value) { 
                _a = value;
            }      
        }
        

        在这种情况下,由于 A 是一个引用类型,它通过 getter 的引用返回。但是现在假设 A 被声明为一个结构(一个值类型)。然后 getter 会返回一个副本。这有时会导致 C# 初学者不明显的问题。

        struct A       // value type
        {
            public Int32 X;     // with a value type field
            public A(Int32 x) { X = x; }
        }
        

        如果 A 是一个结构,并且你尝试这样做

        b.A.X = 11;

        你会得到一个编译器错误:“Cannot modify the return value of 'b.A.X' because it is not a variable”。

        原因是因为b.A返回了一个副本,所以如果你在上面更改X,那行之后的更改就会丢失。您必须替换整个 A 实例:

        A a = new A(10);   // struct
        B b = new B(a);    // class containing a struct
        
        
        Console.WriteLine(b.A.X);   // 10
        
        a.X = 11;
        Console.WriteLine(b.A.X);   // 10  - because A is a value type now
        
        b.A = new A(11);            // setting a property on B works as expected,
                                    // but you can't do b.A.X = 11
        
        Console.WriteLine(b.A.X);   // 11
        

        附:在 C# 中,您通常会编写 int 而不是 Int32


        1string 是一个引用类型,但是,它是不可变的(所有“修改”字符串的操作都返回单独的string 实例)。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-11-29
          • 2017-04-29
          • 2021-04-08
          • 2015-08-25
          • 1970-01-01
          • 2014-07-03
          • 2023-03-14
          • 1970-01-01
          相关资源
          最近更新 更多