참조하는 대상에 새로운 인스턴스가 할당되었을 때

객체지향 프로그래밍을 하다보면 개체를 참조하는 것으로 개체와 개체간 다양한 관계를 맺어 문제를 풀게 되는데요,

참조하고 있는 개체가 새로운 인스턴스가 되었을 경우 참조하고 있는 쪽에서는 알아서 감지를 할 수는 없습니다. 이런 모양새가 되죠.

이것을 개선할 방법을 코드로 살펴보았습니다.

Console.WriteLine("orignal = new IntValue(15)");
var orignal = new IntValue(15);
var refInstance = orignal;
var refInstance2 = (Ref<IntValue>)(() => orignal);
ref var refInstance3 = ref orignal;
Console.WriteLine($"orign\t: {orignal}");
Console.WriteLine($"=\t: {refInstance}");
Console.WriteLine($"Ref<T>\t: {refInstance2.Get()}");
Console.WriteLine($"ref\t: {refInstance3}");

Console.WriteLine();

orignal = new IntValue(2);
Console.WriteLine("orignal = new IntValue(2)");

Console.WriteLine($"orign\t: {orignal}");
Console.WriteLine($"=\t: {refInstance}");
Console.WriteLine($"Ref<T>\t: {refInstance2.Get()}");
Console.WriteLine($"ref\t: {refInstance3}");

record IntValue(int Value);

readonly record struct Ref<T>(Func<T> Get)
{
    //public static explicit operator Ref<T>(Func<T> Get) => new Ref<T>(Get);
    public static implicit operator Ref<T>(Func<T> Get) => new(Get);
}

| 출력

orignal = new IntValue(15)
orign   : IntValue { Value = 15 }
=       : IntValue { Value = 15 }
Ref<T>  : IntValue { Value = 15 }
ref     : IntValue { Value = 15 }

orignal = new IntValue(2)
orign   : IntValue { Value = 2 }
=       : IntValue { Value = 15 }
Ref<T>  : IntValue { Value = 2 }
ref     : IntValue { Value = 2 }

이런 경우 여러분은 어떻게 해결하시나요?

5개의 좋아요

객체라면 복사 생성자, 할당 연산자 오버로딩
스마트 포인터 클래스에서 필수적인 구현이죠

1개의 좋아요

저한테는 난해한 주제네요.

참조 객체가 바뀐 것을 알 필요가 있는 케이스가 언제이며,
보여주신 코드가 어떻게 알려준다는 것인지…

이해하기 힘듭니다. ㅠㅠ

요 두개만 있어도 original에 새로운 인스턴스가 있다는 것을 알 수 있는 것 아닌가요?

1개의 좋아요

아 참조하는 인스턴스가 변경된 케이스 입니다.

위의 그림과 같이 IntValue(15)에서 새로운 인스턴스인 IntValue(2)로 바뀌었음에도 refInstance는 IntValue(15)를 가리키죠. 알아서 변경된 인스턴스 IntValue(2)를 가리키면 좋을텐데 말이죠.

이런 케이스가 다른 분들도 있으셨을까 궁금해서 올렸습니다.

2개의 좋아요

그런 의미라면 아래의 두 개 만으로도 알 수 있는 것 아닌가요?

이 주제에 합당한 얘기지는 잘 모르겠지만, 저는 사실 자주 호출되는 메서드라면 반환보다는 in, ref, out 을 애용하는 편입니다.

void GetValue(in MyData data, out ValueType value)
{
} 

변수에 대한 할당/복사도 막고, return 도 복사이기 때문에, 성능이 최소 두 배 이상 좋아지거든요.
특히 필터링할 데이터가 많을 경우, 반드시 씁니다. 10배 정도 속도 차이가 나는 것도 경험을 했거든요.

3개의 좋아요

Rust로 한 번 해봤어요

    let mut original = 15;
    let ref_inst = &original.clone();
    println!("original = {}", original);
    println!("refInst = {}\n", ref_inst);

    original = 2;
    println!("original = {}", original);
    println!("refInst = {}", ref_inst);

출력:
original = 15
refInst = 15

original = 2
refInst = 15

let ref_inst = &original.clone(); 클론으로 복제하지 않으면 오류납니다.
cannot assign to ‘original’ because it is borrowed

2개의 좋아요

그냥 스치듯 든 생각은… setter 내부 구현입니다… :blush:

2개의 좋아요

번뜩 떠오른 생각으로는,
Notify를 하게 되는 가장 흔한 케이스가 이런 이유 아닐까 싶네요 ㅎㅎ;

1개의 좋아요