C#의 인터페이스 기반 프로그래밍으로 작업자 과부하
-
05-09-2019 - |
문제
배경
현재 프로젝트에서 인터페이스 기반 프로그래밍을 사용하고 있으며 오퍼레이터 (특히 평등 및 불평등 운영자)를 과부하시킬 때 문제가 발생했습니다.
가정
- 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)
- 인터페이스에서 구현을 지정할 수 없습니다.
문제를 보여주는 코드 및 출력을 참조하십시오.
의문
인터페이스 기반 프로그래밍을 사용할 때 클래스에 적절한 운영자 과부하를 어떻게 제공합니까?
참조
사전 정의 된 값 유형의 경우, 동등한 연산자 (==)는 피연산자의 값이 동일하고 그렇지 않으면 참된 경우 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] 속성을 가지고 있습니다.