긴 자료형에서 타이핑/가독성 이득을 보고, 변수명을 뚜렷하게 네이밍하고 var 사용하는 방식을 채택하는게 좋다.
다만, 내장 숫자 타입(int, float, double 등)을 선언할 때는 명시적으로 타입을 선언하는 편이 낫다.
런타임 상수는 컴파일 상수보다 성능이 약간 떨어지지만 유연성이 높고, 어셈블리를 재컴파일을 하지않아도 된다.
static readonly로 사용 시, 인스턴스 별로 다른 값을 갖을 수 있다. 생성자에서 정의할 수 있기 때문이다.
다만, 컴파일할 때 사용되는 상숫값을 정의할 때는 const를 사용한다. 특성의 매개변수, switch/case 문의 레이블, enum 정의 시 사용하는 상수등.
더 안전하고 런타임에 더 효율적으로 동작한다.
캐스트는 예외처리와 null확인코드 둘다 필요하지만, is as는 null확인코드만 있으면 된다.
variable 변수는 var i = o as int?
이런식으로 쓰면 된다.
핵심은 is와 as 연산자는 거의 항상 예상대로 동작하며 대상 객체를 올바르게 형변환할 수 있을 경우에만 성공한다.
코드 가독성 대폭향상과 정적 타입 검사를 수행할 수 있기에 개발자의 실수를 미연에 방지 가능.
문자열 앞에 $ 문자열로 변경할 표현식은 { } 중괄호 안에 @는 축자문자열(그대로 표현)로 표현한다.
예제
Console.WriteLine($"The value of pi is {Math.PI:F2}");
Console.WriteLine($@"The value of pi is {(round ? Math.PI.ToString() : Math.PI.ToString("F2"))}");
Console.WriteLine($"The customer's name is {c?.Name ?? "Name is missiong"}");
보간 문자열 안에 보간 문자열을 넣을 수도 있다.
string result = default(string);
Console.WriteLine($@"Record is {(records.TryGetValue(index,out result) ? result : $"No record found at index {index}")}");
LINQ 응용
var output = $@"The First five items are: {src.Take(5).Select(n => $@"Item: {n.ToString()}").Aggregate((c,a) => $@"{c}{Environment.NewLine}{a}")}";
문자열 보간의 결과는 문자열이라는 점을 인지해야한다. 매개변수화 된 SQL쿼리를 생성하지 않는다.
보통 선언방법
FormattableString second = $"It's the {DateTime.Now.Day} of the {DateTime.Now.Month} month";
var로 선언하면 string 객체 뿐 아니라 FormattableString을 상속한 타입의 객체가 될 수 도 있다.
단, 문자열을 매개변수로 취하는 메서드에 이 변수를 전달하는 코드를 작성하면 안 된다.
var third = $"It's the {DateTime.Now.Day} of the {DateTime.Now.Month} month";
서로 다른 시스템 사이에서 데이터를 주고받을 때 심볼 그 자체를 해당 심볼을 포함하는 문자열로 대체해주는 역할이다.
함수 이름을 매개변수로 처리할때나 예외 처리 입력할때 유용
항상 정규화된 이름(ex System.Int.MaxValue)이 아닌 로컬 이름(MaxValue)을 반환한다는 점을 인지해야한다.
INotifyPropertyChanged 인터페이스 구현부
public string Name
{
get
{
return name;
}
set
{
if(value!=name)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
}
}
}
private string name;
콜백을 위한 표현방법으로 (매개변수) => {(내용)} 으로 표현된다.
Predicate 조건을 검사하여 bool값을 반환하는 델리게이트
Action<> 여러 개의 매개변수를 받지만 반환 타입이 void인 델리게이트
Func<> 여러 개의 매개변수를 받아 단일의 결괏값을 반환하는 델리게이트
예외에 안전하지 않고, 마지막으로 호출된 대상 함수의 반환값이 델리게이트의 반환값으로 간주 된다는 것을 주의해야한다.
Func<bool> cp = () => CheckWithUser();
cp += () => CheckWithSystem();
c.LengthyOperation(cp);
이런식으로 멀티캐스트 델리게이트 형태로 사용하면 체인에서 마지막으로 호출된 함수의 반환값만 적용되고, 이 문제를 해결하려면 리스트를 살펴서 직접 호출하는 방식으로 코드를 작성해야한다.
pubilc void LengthyOperation2(Func<bool> pred)
{
bool bContinue = true;
foreach(ComplicatedClass cl in container)
{
cl.DoLenghyOperation();
foreach(Func<bool> pr in pred.GetInvocationList())
bContinue &= pr();
if(!bContinue) return;
//여기 통과하면 모두 true인 상태니까 코드 실행 이런식으로
}
}
public void RaiseUpdates()
{
counter++;
if(Updated != null)
Updated(this, counter);
}
이 구문의 문제는 if문을 호출하여 Updated 이벤트가 null이 아님을 확인했지만, 다른 스레드가 직전에 이벤트 핸들러 등록을 취소할경우 null값을 가질 수 있는 경우가 생기기 때문이다.
public void RaiseUpdates()
{
counter++;
var handler = Updated;
if(handler != null)
handler(this, counter);
}
멀티 스레드 환경에서도 안전하게 동작하게 하는 권장 코드 다른 스레드가 구독 취소하면 복사된 지역변수의 내용은 변경되지 않는다. 다만, 필요없는 코드처럼 보이기도 한다. null 조건 연산자를 사용하면 코드를 매우 간단하게 작성할 수 있다.
public void RaiseUpdates()
{
counter++;
Updated?.Invoke(this, counter);
}
왼쪽에 Invoke함수가 없으면 실행을 안한다. 이 방식은 기존방식보다 더욱 단순하고 명확하기에 무조건 사용해도 좋다.
박싱과 언박싱은 System.Object, 인터페이스 타입이 필요한 곳에서 값 타입을 사용하기 위해 반드시 필요한 메커니즘이다.
성능문제 뿐 아니라 임시 객체로 인해 예상치 못한 버그가 발생하기도 한다.
대부분의 경우 제너릭 클래스와 제너릭 메서드를 사용해서 박싱과 언박싱을 피할 수 있다.
object firstParm =5;
object o = firstParm;
int i = (int)o; //언박싱
string output = i.ToString();
컴파일러가 값 타입 객체를 System.Object로 변경하기 위해 자동으로 생성하는 코드
메서드에 값 타입 객체를 직접 전달하지 말고 문자열 인스턴스를 전달하는 게 좋다.
Console.WriteLine($@"A few numbers:{firstNumber.ToString()},{secondNumber.ToString()},{thirdNumber.ToString()}");
System.Object 타입으로 박싱이 이루어지는지 유심히 살펴보고 이를 개선해야한다.
pubilc class MyWidget : BaseWidget
{
pubilc new void NormalizeValues()
{
base.NormalieValue();
}
}
이런 new로 동일한 메서드를 선언하는건, 웬만하면 하지 않는게 좋다.
정적 변수와 인스턴스 변수 어느 쪽이라도 가능한 멤버 초기화 구문을 사용하는 것이 읽기 쉽고 유지보수도 좋다.
멤버 초기화 순서는 변수의 선언 순서대로 수행되며, 생성자 전에 멤버 초기화가 먼저 실행된다는 점을 알고 있으면 좋다.
public class MyClass
{
// 컬랙션을 선언하는 동시에 초기화
private List<string> labels = new List<string>();
}
null이나 0으로 초기화 하는 경우, 객체를 생성하는 방식이 다양하게 혼재할 경우 안하는게 맞다. 예외처리가 필요한 경우에도 생성자에 하는게 맞다.
정적 멤버 초기화 구문과 정적 생성자 둘 중에 하나로 초기화 해야한다.
public class MySingleton
{
private static reaadonly MySingleton theOneAndOnly = new MySingleton(); //정적 멤버 초기화 구문
public static MySingleton TheOnly
{
get
{
return theOneAndOnly;
}
}
private MySingleton() //인스턴스 생성자 사용금지
{
}
}
public class MySingleton2
{
private static reaadonly MySingleton2 theOneAndOnly;
static MySingleton2() //정적 생성자 이용
{
theOneAndOnly = new MySingleton2();
}
public static MySingleton2 TheOnly
{
get
{
return theOneAndOnly;
}
}
private MySingleton2() //인스턴스 생성자 사용금지
{
}
}
예외가 발생할 가능성이 있는 경우에는 반드시 정적 생성자를 사용해야한다.
public class MySingleton2
{
private static reaadonly MySingleton2 theOneAndOnly;
static MySingleton2() //정적 생성자 이용
{
try
{
theOneAndOnly = new MySingleton2();
}
catch
{
}
}
public static MySingleton2 TheOnly
{
get
{
return theOneAndOnly;
}
}
private MySingleton2() //인스턴스 생성자 사용금지
{
}
}
public class MyClass
{
//데이터 컬렉션
private List<ImportantData> coll;
//인스턴스 변수
private string name;
//new() 제약 조건을 만족시키려면 이 생성자가 필요하다.
public MyClass() : this(0, string.Empty)
{
}
public MyClass(int initialCount = 0, string name = "")
{
coll = (initialCount > 0) ? new List<ImportantData>(initialCount) : new List<ImportantData>();
this.name = name;
}
}
기본 매개변수와 this 응용해서 이런식으로 표현하는게 좋다.
아래 예제는 잘못된 예
public class MyClass
{
private List<ImportantData> coll;
private string name;
public MyClass()
{
commonConstructor(0, "");
}
public MyClass(int initialCount)
{
commonConstructor(initialCount, "");
}
public MyClass(int initialCount, string Name)
{
commonConstructor(initialCount, Name);
}
private void commonConstructor(int count, string name)
{
coll = (count > 0) ? new List<ImportantData>(count) : new List<ImportantData>();
this.name = name;
}
}
자주 사용되는 지역변수를 멤버 변수로 변경하는 것
종속적 삽입을 활용하여 자주 사용되는 객체를 생성했다가 이를 재활용하는 것
private static Brush blackBrush;
pubilc static Brush Black
{
get
{
if(blackBrush == null)
blackBrush = new SolidBrush(Color.Black);
return blackBrush;
}
}
immutable 타입 System.String 같은 객체의 내용을 수정할 수 없어서 새로 생성된다. 이럴땐 문자열 보간이나 더 복잡하다면 StringBulider를 사용하자
string msg = string.Format("Hello, {0}. Today is {1}", thisUser.Name, DateTime.Now.ToString());
StringBuilder msg = new StringBuilder("Hello, ");
msg.Append(thisUser.Name);
msg.Append(". Today is ");
msg.Append(DateTime.Now.ToString());
string finalMsg = msg.ToString();
이건 아직도 이해하기 어렵다. 패스