Development Tip

C #에서 인터페이스 기반 프로그래밍을 사용한 연산자 오버로딩

yourdevel 2020. 10. 27. 23:33
반응형

C #에서 인터페이스 기반 프로그래밍을 사용한 연산자 오버로딩


배경

현재 프로젝트에서 인터페이스 기반 프로그래밍을 사용하고 있으며 연산자 (특히 Equality 및 Inequality 연산자)를 오버로드 할 때 문제가 발생했습니다.


가정

  • C # 3.0, .NET 3.5 및 Visual Studio 2008을 사용하고 있습니다.

업데이트-다음 가정은 거짓이었습니다!

  • operator == 대신 Equals를 사용하기 위해 모든 비교를 요구하는 것은 특히 유형을 라이브러리 (예 : Collections)에 전달할 때 실행 가능한 솔루션이 아닙니다.

operator == 대신 Equals를 사용하도록 요구하는 것에 대해 염려 한 이유는 .NET 지침에서 operator == 대신 Equals를 사용하거나 심지어 제안 할 것이라고 언급 한 곳을 찾을 수 없었기 때문입니다. 그러나 등식 및 연산자 == 재정의에 대한 지침을 다시 읽은 후 다음 을 발견했습니다.

기본적으로 == 연산자는 두 참조가 동일한 객체를 나타내는 지 확인하여 참조 동등성을 테스트합니다. 따라서 참조 유형은이 기능을 얻기 위해 == 연산자를 구현할 필요가 없습니다. 유형이 불변 인 경우, 즉 인스턴스에 포함 된 데이터를 변경할 수없는 경우 연산자 ==를 오버로딩하여 참조 같음 대신 값 같음을 비교하는 것이 유용 할 수 있습니다. 왜냐하면 불변 객체로서 long과 동일한 것으로 간주 될 수 있기 때문입니다. 같은 가치를 가지고 있기 때문입니다. 변경 불가능한 유형에서 == 연산자를 재정의하는 것은 좋은 생각이 아닙니다.

그리고이 Equatable 인터페이스

IEquatable 인터페이스는 Contains, IndexOf, LastIndexOf 및 Remove와 같은 메서드에서 동등성을 테스트 할 때 Dictionary, List 및 LinkedList와 같은 일반 컬렉션 개체에서 사용됩니다. 일반 컬렉션에 저장 될 수있는 모든 개체에 대해 구현되어야합니다.


구속

  • 모든 솔루션은 인터페이스에서 구체적인 유형으로 객체를 캐스팅 할 필요가 없어야합니다.

문제

  • operator ==의 양쪽이 모두 인터페이스이면 기본 구체적인 유형의 operator == 오버로드 메서드 서명이 일치하지 않으므로 기본 Object operator == 메서드가 호출됩니다.
  • 클래스에서 연산자를 오버로드 할 때 이항 연산자의 매개 변수 중 하나 이상이 포함 유형이어야합니다. 그렇지 않으면 컴파일러 오류가 생성됩니다 (오류 BC33021 http://msdn.microsoft.com/en-us/library/watt39ff .aspx )
  • 인터페이스에서 구현을 지정할 수 없습니다.

문제를 보여주는 아래의 코드 및 출력을 참조하십시오.


질문

인터페이스 기반 프로그래밍을 사용할 때 클래스에 적절한 연산자 오버로드를 어떻게 제공합니까?


참고 문헌

== 연산자 (C # 참조)

미리 정의 된 값 유형의 경우 항등 연산자 (==)는 피연산자의 값이 같으면 true를 반환하고 그렇지 않으면 false를 반환합니다. 문자열이 아닌 참조 유형의 경우 ==는 두 피연산자가 동일한 객체를 참조하면 true를 반환합니다. 문자열 유형의 경우 ==는 문자열 값을 비교합니다.


또한보십시오


암호

using System;

namespace OperatorOverloadsWithInterfaces
{
    public interface IAddress : IEquatable<IAddress>
    {
        string StreetName { get; set; }
        string City { get; set; }
        string State { get; set; }
    }

    public class Address : IAddress
    {
        private string _streetName;
        private string _city;
        private string _state;

        public Address(string city, string state, string streetName)
        {
            City = city;
            State = state;
            StreetName = streetName;
        }

        #region IAddress Members

        public virtual string StreetName
        {
            get { return _streetName; }
            set { _streetName = value; }
        }

        public virtual string City
        {
            get { return _city; }
            set { _city = value; }
        }

        public virtual string State
        {
            get { return _state; }
            set { _state = value; }
        }

        public static bool operator ==(Address lhs, Address rhs)
        {
            Console.WriteLine("Address operator== overload called.");
            // If both sides of the argument are the same instance or null, they are equal
            if (Object.ReferenceEquals(lhs, rhs))
            {
                return true;
            }

            return lhs.Equals(rhs);
        }

        public static bool operator !=(Address lhs, Address rhs)
        {
            return !(lhs == rhs);
        }

        public override bool Equals(object obj)
        {
            // Use 'as' rather than a cast to get a null rather an exception
            // if the object isn't convertible
            Address address = obj as Address;
            return this.Equals(address);
        }

        public override int GetHashCode()
        {
            string composite = StreetName + City + State;
            return composite.GetHashCode();
        }

        #endregion

        #region IEquatable<IAddress> Members

        public virtual bool Equals(IAddress other)
        {
            // Per MSDN documentation, x.Equals(null) should return false
            if ((object)other == null)
            {
                return false;
            }

            return ((this.City == other.City)
                && (this.State == other.State)
                && (this.StreetName == other.StreetName));
        }

        #endregion
    }

    public class Program
    {
        static void Main(string[] args)
        {
            IAddress address1 = new Address("seattle", "washington", "Awesome St");
            IAddress address2 = new Address("seattle", "washington", "Awesome St");

            functionThatComparesAddresses(address1, address2);

            Console.Read();
        }

        public static void functionThatComparesAddresses(IAddress address1, IAddress address2)
        {
            if (address1 == address2)
            {
                Console.WriteLine("Equal with the interfaces.");
            }

            if ((Address)address1 == address2)
            {
                Console.WriteLine("Equal with Left-hand side cast.");
            }

            if (address1 == (Address)address2)
            {
                Console.WriteLine("Equal with Right-hand side cast.");
            }

            if ((Address)address1 == (Address)address2)
            {
                Console.WriteLine("Equal with both sides cast.");
            }
        }
    }
}

산출

Address operator== overload called
Equal with both sides cast.

Short answer: I think your second assumption may be flawed. Equals() is the right way to check for semantic equality of two objects, not operator ==.


Long answer: Overload resolution for operators is performed at compile time, not run time.

Unless the compiler can definitively know the types of the objects it's applying an operator to, it won't compile. Since the compiler cannot be sure that an IAddress is going to be something that has an override for == defined, it falls back to the default operator == implementation of System.Object.

To see this more clearly, try defining an operator + for Address and adding two IAddress instances. Unless you explicitly cast to Address, it will fail to compile. Why? Because the compiler can't tell that a particular IAddress is an Address, and there is no default operator + implementation to fall back to in System.Object.


Part of your frustration probably stems from the fact that Object implements an operator ==, and everything is an Object, so the compiler can successfully resolve operations like a == b for all types. When you overrode ==, you expected to see the same behavior but didn't, and that's because the best match the compiler can find is the original Object implementation.

Requiring all comparisons to use Equals rather than operator== is not a viable solution, especially when passing your types to libraries (such as Collections).

In my view, this is precisely what you should be doing. Equals() is the right way to check for semantic equality of two objects. Sometimes semantic equality is just reference equality, in which case you won't need to change anything. In other cases, as in your example, you'll override Equals when you need a stronger equality contract than reference equality. For example, you may want to consider two Persons equal if they have the same Social Security number, or two Vehicles equal if they have the same VIN.

But Equals() and operator == are not the same thing. Whenever you need to override operator ==, you should override Equals(), but almost never the other way around. operator == is more of a syntactical convenience. Some CLR languages (e.g. Visual Basic.NET) don't even permit you to override the equality operator.


We ran into the same problem, and found an excellent solution: Resharper custom patterns.

We configured ALL of our users to use a common global pattern catalog in addition to their own, and placed it into SVN so that it can be versioned and updated for everyone.

The catalog included all patterns known to be wrong in our system:

$i1$ == $i2$ (where i1 and i2 are expressions of our interface type, or derived.

the replace pattern is

$i1$.Equals($i2$)

and the severity is "Show as error".

Similarly we have $i1$ != $i2$

Hope this helps. P.S. Global catalogs is the feature in Resharper 6.1 (EAP), will be marked as final very soon.

Update: I filed a Resharper Issue to mark all interface '==' a warning unless it is comparing to null. Please vote if you think it is a worthy feature.

Update2: Resharper also has [CannotApplyEqualityOperator] attribute that can help.

참고URL : https://stackoverflow.com/questions/728434/operator-overloading-with-interface-based-programming-in-c-sharp

반응형