【问题标题】:Interfaces are not passed by reference接口不通过引用传递
【发布时间】:2015-05-22 13:11:57
【问题描述】:

我需要在运行时更改接口的实现。这个接口被很多类引用。

这是我的测试用例,它没有像我预期的那样工作。 (更改接口的引用似乎并没有在所有使用它的地方更新)

示例如下:

// interface to change at runtime
interface IDiet
{
    void Eat();
}

class CarnivoreDiet : IDiet
{
    public void Eat()
    {
        Debug.WriteLine("Eat chicken");
    }
}

class HerbivoreDiet : IDiet
{
    public void Eat()
    {
        Debug.WriteLine("Eat spinach");
    }
}

class Human : IDiet
{
    private readonly IDiet _diet;
    public Human(IDiet diet)
    {
        _diet = diet;
    }

    public void Eat()
    {
        _diet.Eat();
    }
}

void Main()
{
    IDiet diet = new CarnivoreDiet();
    Human human = new Human(diet);
    human.Eat();
    // outputs "Eat chicken"

    diet = new HerbivoreDiet();
    human.Eat();
    // still outputs "Eat chicken" even if i changed the reference of IDiet interface
}

为什么IDiet 接口没有在Human 实例内部更新?

PS:IDiet 接口在很多类中都有使用,所以添加SetDiet(IDiet diet) 之类的方法不会解决问题。

【问题讨论】:

  • 你期望什么,你观察到什么行为? “不更新”是什么意思?参考是只读的;设置后,引用应该是不可变的。
  • 因为 Human 拥有参考的副本 (_diet) 而您正在修改参考的另一个副本 (diet)
  • @Rahul 这本质上是构造函数注入。我认为 IoC 或 DI 不会改变任何事情。
  • 接口在这里无关紧要——我认为您从根本上需要重新审视 C# 的工作原理。特别是,您似乎希望 human._diet 字段链接到 diet 局部变量。不是 - diet 的值被复制到 diet 参数中,然后该参数被复制到 _diet 字段中。以后对 diet 局部变量的更改是无关紧要的。

标签: c# asp.net pointers reference


【解决方案1】:

当您通过此代码传递对象引用时:

Human human = new Human(diet);

引用(对象的地址)被复制到它的参数中:

public Human(IDiet diet)
{
        _diet = diet;
}

它们是3 个不同的内存块,包含对对象的相同引用:您的原始 diet、参数变量 (diet) 和您的类属性 (_diet)。

所以当你执行你的代码时:

diet = new HerbivoreDiet();

这个内存块现在包含对新对象 (HerbivoreDiet) 的引用,但 Human 中的那个仍然引用旧对象。

【讨论】:

  • 并将参数复制到一个字段:-) 三个不同的内存块,其中一个的持续时间非常有限(参数)
  • @xanatos:是的,我错过了。这是一个错误,但这是造成此问题的主要原因。
【解决方案2】:

既然你已经实现了一个外观,为什么不实现第二个:

class ChangableDiet : IDiet
{
    private IDiet _diet;
    public ChangableDiet (IDiet diet)
    {
        _diet = diet;
    }

    public Diet Diet { get { return _diet;} set { _diet = value; } }

    public void Eat()
    {
        _diet.Eat();
    }
}

构造其中之一并将 it 传递给 Human 构造函数。

void Main()
{
    IDiet diet = new CarnivoreDiet();
    var changable = new ChangableDiet(diet)
    Human human = new Human(changable );
    human.Eat();
    // outputs "Eat chicken"

    changable.Diet = new HerbivoreDiet();
    human.Eat();
    // outputs "Eat spinach"
}

【讨论】:

    【解决方案3】:

    diet 是一个局部变量,它的值是指向内存中实际对象的指针(地址)。

    如果你去diet.SomeProperty = "foo",你改变了内存中的对象,这反映在任何地方。

    如果你说diet = new Diet(),你会用指向另一个对象的新指针覆盖局部变量diet

    Human 引用没有改变,因为指针是按值传递的。他们指向的对象是相同的(直到你覆盖饮食),但指针是彼此的副本。

    您应该将饮食设置为可访问的非只读属性并进行更改。或者创造一个新人类。

    【讨论】:

      【解决方案4】:

      关于问题:为什么IDiet 接口没有在Human 实例内部更新?

      仅仅因为您的接口引用是按值传递的,这意味着您在Human 类中持有的引用是指向同一个IDiet 的副本

      以后做

      diet = new HerbivoreDiet();
      

      您只需更改 Human 类之外的引用,该类仍然是自己的副本,因此没有机会更改其引用。

      这是您可以实施的解决方案:

      public interface IProvider<T>
      {
        public T Current {get; set;}
      }
      
      public class DietProvider : IProvider<IDiet>
      {
        public IDiet Current {get; set;}
      }
      
      class Human : IDiet
      {
        private readonly IProvider<IDiet> _dietProvider;
      
        public Human(IProvider<IDiet> dietProvider)
        {
          _dietProvider = dietProvider;
        }
      
        public void Eat()
        {
          _dietProvider.Current.Eat();
        }
      }
      
      void Main()
      {
        IProvider<IDiet> dietProvider= new DietProvider { Current = new CarnivoreDiet()};
        Human human = new Human(dietProvider);
        human.Eat();
        // outputs "Eat chicken"
      
        dietProvider.Current = new HerbivoreDiet();
        human.Eat();
      }
      

      【讨论】:

        【解决方案5】:

        这与接口无关。 您的代码所做的是将变量按值传递给类构造函数,尽管该值是对实例的引用。然后将此值复制到新创建对象的实例的成员中。然后,当您更改最初用于保存该值的变量中的值时,它对您的类实例中的内部成员没有影响。

        ints 完全相同。考虑这个示例:

        class MyClass {
           private int mInt;
           public MyClass(int theInt) {
              mInt = theInt;
           }
        
           public int GetTheInt() { return mInt; }
        }
        
        void Main()
        {
           int anInt = 5;
           MyClass MyInstance(anInt);
           anInt = 10;
           if(MyInstance.GetTheInt() == anInt)
              // Will never happen
           ;
        }
        

        【讨论】:

          猜你喜欢
          • 2014-02-07
          • 1970-01-01
          • 2013-03-07
          • 2020-12-19
          • 2018-02-14
          • 2016-06-09
          • 1970-01-01
          • 1970-01-01
          • 2016-12-18
          相关资源
          最近更新 更多