문제

배경

현재 프로젝트에서 인터페이스 기반 프로그래밍을 사용하고 있으며 오퍼레이터 (특히 평등 및 불평등 운영자)를 과부하시킬 때 문제가 발생했습니다.


가정

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

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

  • 모든 비교가 연산자보다는 평등을 사용하도록 요구하는 것은 특히 유형을 라이브러리 (예 : 컬렉션)로 전달할 때 실행 가능한 솔루션이 아닙니다.

연산자보다는 동등한 사용을 요구하는 것에 대해 걱정했던 이유는 .NET 가이드 라인의 어느 곳에서나 연산자가 아닌 동등한 것을 사용하거나 심지어 제안 할 것이라고 언급 한 어느 곳에서나 찾을 수 없기 때문입니다. 그러나 다시 읽은 후 평등 및 연산자를 재정의하기위한 지침 == 나는 이것을 발견했다 :

기본적으로 연산자 == 두 참조가 동일한 객체를 나타내는 지 여부를 결정하여 참조 평등을 테스트합니다. 따라서이 기능을 얻으려면 참조 유형이 연산자 ==를 구현할 필요가 없습니다. 유형이 불변 인 경우, 즉 인스턴스에 포함 된 데이터를 변경할 수없고, 오버로드 연산자 == 참조 평등 대신 가치 평등을 비교하려면 불변의 개체로서 오랫동안 동일하게 간주 될 수 있기 때문에 유용 할 수 있습니다. 그들은 같은 가치를 가지고 있기 때문에. 상상할 수없는 유형에서 연산자 ==를 무시하는 것은 좋은 생각이 아닙니다.

평등 한 인터페이스

iquipatable 인터페이스는 contains, indexof, lastIndexof 및 제거와 같은 방법에서 평등을 테스트 할 때 Dictionary, List 및 LinkedList와 같은 일반 수집 객체에서 사용됩니다. 일반 컬렉션에 저장 될 수있는 객체에 대해 구현해야합니다.


금기

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

문제

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

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


의문

인터페이스 기반 프로그래밍을 사용할 때 클래스에 적절한 운영자 과부하를 어떻게 제공합니까?


참조

== 운영자 (C# 참조)

사전 정의 된 값 유형의 경우, 동등한 연산자 (==)는 피연산자의 값이 동일하고 그렇지 않으면 참된 경우 true를 반환합니다. 문자열 이외의 참조 유형의 경우, 두 피연산자가 동일한 객체를 참조하면 ==가 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.
도움이 되었습니까?

해결책

짧은 대답 : 두 번째 가정은 결함이있을 수 있다고 생각합니다. Equals() 확인하는 올바른 방법입니다 시맨틱 평등 두 가지 물체 중 operator ==.


긴 답변 : 운영자의 과부하 해상도는 다음과 같습니다 컴파일 시간에 실행 시간이 아니라 수행됩니다.

컴파일러가 연산자에게 적용하는 객체의 유형을 확실하게 알 수 없다면 컴파일하지 않습니다. 컴파일러는 an IAddress 재정의가있는 것이 될 것입니다. == 정의되면 기본값으로 돌아갑니다 operator == 구현 System.Object.

이것을보다 명확하게 보려면 정의를 시도하십시오 operator + ~을 위한 Address 그리고 두 가지를 추가합니다 IAddress 인스턴스. 명시 적으로 캐스팅하지 않는 한 Address, 컴파일에 실패합니다. 왜요? 컴파일러는 특정 것을 말할 수 없기 때문입니다 IAddress 이다 Address, 그리고 기본값이 없습니다 operator + 다시 떨어질 구현 System.Object.


당신의 좌절의 일부는 아마도 Object an operator ==, 그리고 모든 것이 an입니다 Object, 따라서 컴파일러는 같은 작업을 성공적으로 해결할 수 있습니다 a == b 모든 유형에 대해. 당신이 무시할 때 ==, 당신은 동일한 동작을 볼 것으로 예상되었지만 그렇지 않았으며, 컴파일러가 찾을 수있는 가장 잘 어울리는 것이 원본이기 때문입니다. Object 구현.

모든 비교가 연산자보다는 평등을 사용하도록 요구하는 것은 특히 유형을 라이브러리 (예 : 컬렉션)로 전달할 때 실행 가능한 솔루션이 아닙니다.

내 생각에, 이것은 정확히 당신이해야 할 일입니다. Equals() 확인하는 올바른 방법입니다 시맨틱 평등 두 개체의. 때때로 의미 론적 평등은 단지 참조 평등 일 뿐이며,이 경우 아무것도 바꿀 필요가 없습니다. 다른 경우, 당신의 예에서와 같이, 당신은 Equals 참조 평등보다 더 강한 평등 계약이 필요할 때. 예를 들어, 두 가지를 고려할 수 있습니다 Persons 동일한 사회 보장 번호가있는 경우 동일 Vehicles 그들이 같은 Vin을 가지고 있다면 동일합니다.

하지만 Equals() 그리고 operator == 같은 것이 아닙니다. 재정의가 필요할 때마다 operator ==, 당신은 무시해야합니다 Equals(), 그러나 거의 다른 방법으로는 결코 없습니다. operator == 구문 편의성입니다. 일부 CLR 언어 (예 : Visual Basic.net)는 평등 연산자를 무시할 수 없습니다.

다른 팁

우리는 같은 문제에 부딪 쳤고 훌륭한 해결책을 찾았습니다 : reharper custom patterns.

우리는 모든 사용자가 자체 외에 일반적인 글로벌 패턴 카탈로그를 사용하도록 구성하고 SVN에 배치하여 모든 사람을 위해 버전을 업데이트 할 수 있도록 구성했습니다.

카탈로그에는 시스템에서 잘못된 것으로 알려진 모든 패턴이 포함되었습니다.

$i1$ == $i2$ (여기서 i1과 i2가 있습니다 표현 우리의 인터페이스 유형 또는 파생.

교체 패턴은입니다

$i1$.Equals($i2$)

심각도는 "오류로 표시"입니다.

마찬가지로 우리는 가지고 있습니다 $i1$ != $i2$

도움이 되었기를 바랍니다. PS Global Catalogs는 Resharper 6.1 (EAP)의 기능이며 곧 최종적으로 표시됩니다.

업데이트: 나는 a resharper 문제 NULL과 비교하지 않는 한 모든 인터페이스 '=='경고를 표시합니다. 가치있는 기능이라고 생각되면 투표하십시오.

업데이트 2: Resharper는 또한 도움이 될 수있는 [ApplyequalityOperator] 속성을 가지고 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top