【问题标题】:Assignment operator in C#C#中的赋值运算符
【发布时间】:2016-12-29 18:42:44
【问题描述】:

我知道,与 C++ 不同,在 C# 中无法覆盖赋值运算符 (=),如果我们想将 C 类的实例 i1 分配给另一个实例 i2(类C)。

但问题来了: 我有一个通用类 T

public class Node<T>
{
  public Node<T> Next;
  public Node<T> Previous;
  T Content;

  public Node<T>()
  {
  }
}

T 可以是像 Person 这样的泛型类,也可以是像 int、double 这样的 C# 标准类型...有时需要将 Node 中的内容 T 的值分配(=)另一个内容 T。它如果有一个赋值运算符可以在泛型类 T 中实现,这样 t1=t2 操作将适用于泛型类和标准类型,例如 int、double... 等,那就太好了。

由于 C# 中不允许重载赋值运算符,您有什么建议?我正在寻找一种既适用于泛型类又适用于标准类型的优雅解决方案,这样我的代码就不必区分不同的场景。

有可能做到吗?非常感谢!

【问题讨论】:

  • 在 C# 中,“将 C 类的实例 i1 分配给 C 类的另一个实例 i2”是罕见且非常奇怪的。在 C# 中,我们将有一个可重新绑定的引用 r 到类 C,并且我们会将 C 的新实例分配给 r。在 C# 中,如果 T 是一个类,则 T Content 只能是一个引用。并且 C# 引用是可重新绑定的。
  • C# 中有用于此目的的克隆机制。
  • 为什么this.Content = other.Content 不适合你?你能解释一下吗?另外,考虑使用内置的LinkedList&lt;T&gt; 类型。

标签: c# generics


【解决方案1】:

您可以始终将给定类型的变量分配给该类型的另一个变量。

换句话说,写作:

public void Reassign(T source)
{
    Content = source;
}

将始终有效。您确实必须处理许多类型是 reference 类型,因此所有分配的都是引用。如果您想强制进行值复制,则需要添加一些通用类型约束(ICloneable 或类似的可以工作,但您需要常见 .NET 类型的包装器)。

换句话说,如果你这样做了:

public class Node<T> where T : ICloneable

然后你可以写

public void Assign(T source)
{
    Content = source.Clone();
}

或者,如果您不需要递归地复制值,那么总是有Object.MemberwiseClone

public void Assign(T source)
{
    Content = source.MemberwiseClone();
}

另外,如果您不知道,.NET 在 LinkedList&lt;T&gt; 类中已经有一个双向链表。

【讨论】:

  • 谢谢,布拉德利。我想我有点明白你的建议,但我不得不承认这对我来说还不是很清楚。 (我自己的限制)您能否详细说明如何强制复制内容?我只知道 .Net 中存在链表类,但 tree 也存在吗?在Tree中,当我们删除一个node时,在节点有左右分支的情况下,需要找到合适的叶子,将其复制到我们要删除的节点,然后删除叶子.这就是为什么我认为参考可能不起作用。
  • @user1205746 不,它仍然适用于引用复制(也许您可以详细说明引用类型的混淆之处?把它们想象成指针)。无论如何,我会尝试详细说明值复制。
  • 谢谢!说它仍然可以工作,你的意思是即使我删除叶子,引用仍然有效,因为有另一个“指针”指向“已删除”叶子的内容,垃圾收集器会保留它吗?那样的话,消耗的内存应该翻倍,因为应该删除的不一定要删除?
  • 您可以在此处了解有关 C# 中 BST 的更多信息:msdn.microsoft.com/en-us/library/ms379573(v=vs.80).aspx
  • @user1205746 对于你的树,假设当前对垂死叶子的唯一引用在父节点中,而对我们的“T”实例的唯一引用在叶子中。如果您只是将父子引用分配给任何新叶子,则该叶子的引用计数变为 0 并且它被垃圾收集,这导致“T”的计数实例变为 0 并被收集。但是,如果您想保存“T”的实例,您可以将该引用分配给某些东西(现在引用计数为 2),因此当叶子死亡时,它的引用计数为 1
【解决方案2】:

您不能重载赋值运算符,但可以创建显式或隐式类型转换运算符。这使得在您的情况下不需要重载分配:

public static implicit operator Node<T>(T value) {
    Node<T> node = new Node<T>();
    node.Content = value;
    return node;
}

这使得这项任务完全可能:

int value = 1;
Node<int> node = value;

如果将implicit 替换为explicit,则需要显式类型转换:

int value = 1;
Node<int> node = (Node<T>)value;

您还可以创建相反方向的转换:

public static implicit operator T(Node<T> node) {
    return node.Content;
}

所以你可以给节点赋值:

Node<int> node = new Node<int>();
int value = node;

你也可以转换成不同的节点类型:

public static implicit operator Node<double>(Node<T> node) {
    Node<double> destinationNode = new Node<double>();
    destinationNode.Content = Convert.ToDouble(node.Content);
    return destinationNode;
}

这正是你所需要的。

【讨论】:

  • 谢谢,Sefe。如果 T 是一个类,假设 Person 具有 3 个属性,ID、FirstName、LastName。你会怎么分配?
  • 这个答案虽然正确,但似乎并没有解决实际问题。
【解决方案3】:

由于您创建的任何模板类或对象都必须采用数据类型,因此您应该始终能够将一个分配给另一个。例如,当您初始化 Node&lt;T&gt; 时,它仍然必须采用数据类型:

Node<double> myNode = new Node<double>();

即使您有不同的实例,具有不同的数据类型,您也可以使用 C# 的 Convert 方法将它们从一种转换为另一种:https://msdn.microsoft.com/en-us/library/system.convert.aspx

Node<int> mySecondNode = new Node<int>();
Node<string> myThirdNode = new Node<string>();
Node<bool> myFourthNode = new Node<bool>();

myNode.Content = Convert.ToDouble(mySecondNode.Content);
myThirdNode.Content = myNode.ToString();
myFourthNode = Convert.ToBoolean(mySecondNode.Content % 2);

等等......重载运算符非常棒;但是 C# 让你很容易。此外,正如 Bradley 所说,C# 库有一个 LinkedList 类,所以你不需要做所有繁重的工作。在这里查看: https://msdn.microsoft.com/en-us/library/he2s3bh7(v=vs.110).aspx

【讨论】:

    【解决方案4】:

    让我提出一个稍微不同的结构,可以完全自定义内容类型。

    public class Node<T> where T : Node<T>
    {
        public T Next { get; set; }
        public T Previous { get; set; }
    }
    

    你不能直接使用这个类,但是你可以从它继承你的内容类型。例如:

    public class Student : Node<Student>
    {
        public int ID { get; set; }
        public string Name { get; set; }
    
        public override string ToString() { return string.Format("ID={0}, Name={1}", ID, Name); }
    }
    

    现在每个Student同时是内容以及列表节点。 Node&lt;T&gt; 类是可重用的。

    现在您可以向节点类添加一些智能。例如:

    public class Node<T> where T : Node<T>
    {        
        public T Next { get; set; }
        public T Previous { get; set; }
        public bool IsRoot { get { return Previous==null; } }
        public bool IsLeaf { get { return Next==null; } }
    
        public int CountBefore { get { return IsRoot ? 0 : Previous.CountBefore+1; } }
        public int CountAfter { get { return IsLeaf ? 0 : Next.CountAfter+1; } }
    
        public T InsertAfter(T node)
        {
            var temp = this.Next;
            this.Next=node;
            if(node!=null)
            {
                node.Next=temp;
                node.Previous=this as T;
            }
            if(temp!=null)
            {
                temp.Previous=node;
            }
            return node;
        }
        public T InsertBefore(T node)
        {
            var temp = this.Previous;
            this.Previous=node;
            if(node!=null)
            {
                node.Previous=temp;
                node.Next=this as T;
            }
            if(temp!=null)
            {
                temp.Next=node;
            }
            return node;
        }
    }
    

    可以这样使用:

    class Program
    {
        static void Main(string[] args)
        {
            var A = new Student() { ID=101, Name="Peter" };
            var B = A.InsertAfter(new Student() { ID=102, Name="John" });
            var C = B.InsertBefore(new Student() { ID=103, Name="Mary" });
    
            //          [A]----[C]----[B]
            //           |      |      |
            // ID       101    103    102
            // Name    Peter  Mary   John
            // IsRoot  true   false  false 
            // IsLeft  false  false  true
            // CountL    0      1      2
            // CountR    2      1      0
    
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-01-24
      • 2011-07-29
      • 1970-01-01
      • 2018-01-27
      • 2021-08-10
      • 2011-03-11
      • 2012-04-22
      • 2015-06-01
      相关资源
      最近更新 更多