[MS-SQL] 연결 된 서버 만들기 by chris

두 개의 MS SQL 서버가 존재한다고 가정을 하겠습니다. 그리고 서버의 이름은 SVR1과 SVR2라고 가정을 하겠습니다. 이런 상황에서 SVR1의 데이터베이스에 연결이 된 상태에서 SVR2 서버의 데이터베이스 내용을 참고해야 하는 경우가 발생 할 수 있습니다. 실제 업무에서 본다면 회계용 MS SQL 서버에 접속한 상태에서 특별한 목적으로 인사용 MS SQL 서버의 데이터베이스의 내용이 필요한 경우가 발생할 수 있습니다.

얼핏 생각하기에 두 서버가 MS SQL 서버가 설치가 되어 있고 네트워크로 연결이 되어 있으므로 그냥 쿼리문을 수행하면 될거라 생각하지만 그렇지 않습니다.

이 처럼 다른 MS SQL와 연결하여 작업을 해야하는 경우 사용되는 것이 연결된 서버(Linked Server)입니다. 연결된 서버를 이용하게 되면 다른 서버에 위치하고 있는 데이터베이스를 손쉽게 접근할 수 있으며, 심지어는 MS SQL 서버가 아니더라도 다른 종류의 데이터를 쉽게 접근 할 수 있습니다. 예를 들어 엑셀 파일을 데이터베이스 서버처럼 사용 할 수 도 있고 심지어 텍스트 파일도 데이터베이스 서버처럼 사용을 할 수 도 있습니다. 이에 대한 내용들을 살펴보도록 하겠습니다.

1. 연결된 서버 만들기

대부분의 작업들이 그러하듯이 연결된 서버를 만드는 것은 EM(Enterprise Manager)를 이용해서도 가능하고 QA(Query Analyzer)를 이용해서도 가능합니다. 이 두가지 방법은 순서대로 살펴보도록 하겠습니다.

그런데 이 강좌에 있어 전제 사항이 있습니다. 연결된 서버의 경우 두대의 MS SQL 서버를 연결해야 하는데 이 강좌를 작성하고 있는 지금 제게는 두대의 MS SQL 서버가 없습니다. 이런 이유도 강좌를 작성하는데는 두개의 MS SQL 서버 인스턴스를 사용하도록 하겠습니다. MS SQL 서버 2000은 하나의 서버에 여러개의 MS SQL 서버를 만들 수 있습니다. 이것이 인스턴스 입니다. 처음에 기본 인스턴스로 설치되면 이때 설치된 MS SQL 서버가 서버 이름이 되며 그 이후에 설치되는 MS SQL 서버는 "서버이름\인스턴스이름"을 이름으로 갖게 됩니다.

이 강좌에서는 다음의 인스턴스가 사용됩니다.

FUTURE
FUTURE\INST01

위 두개의 인스턴스가 개별적인 MS SQL 서버라고 생각하시고 강좌를 이해하시면 됩니다. 다음 [그림 1]은 두개의 인스턴스가 설치어 EM에 등록된 모습을 보여주고 있습니다.


[그림 1]

이제 하려고 하는 것은 FUTURE 서버에 접속을 해서 FUTURE\INST01 서버의 데이터베이스를 사용 할 수 있도록 FUTURE\INST01서버를 FUTURE 서버에 연결된 서버로 등록하는 것입니다. 만일 지금 상황에서 FUTURE 서버에 로그인 해서 다음과 같이 FUTURE\INST01 서버의 Pubs 데이터베이스에서 검색을 하려고 하면 어떻게 될까요?

 

SELECT * FROM [FUTURE\INST01].Pubs.dbo.Titles

다음과 같은 에러가 발생하게 됩니다.

 

서버: 메시지 7202, 수준 11, 상태 2, 줄 1
sysservers에서 'FUTURE\INST01' 서버를 찾을 수 없습니다. sp_addlinkedserver를 실행하여 sysservers에 서버를 추가하십시오.

위 에러메세지만 보아도 연결된 서버(Linked Server)를 만들어야 위 쿼리문이 제대로 수행되며 연결된 서버를 만드는 시스템 저장프로시져가 sp_addlinkedserver 임을 알 수 있습니다. 이 저장프로시져는 뒤에서 살펴보도록 하고 우선 EM을 통해 연결된 서버를 만들어 보도록 하겠습니다.

EM에서 연결된 서버를 등록하는 부분은 다음 [그림 2]와 같이 [보안] 부분의 [연결된 서버] 부분 입니다. 현재는 연결된 서버가 전혀 등록되어 있지 않음을 알 수 있습니다.


[그림 2]

[그림 2]의 "연결된 서버" 에서 마우스 오른쪽 버튼을 눌러 표시되는 단축 메뉴에서 "새 연결된 서버(S)"를 선택하면 다음 [그림 3]과 같이 "연결된 서버 속성" 대화 창이 표시됩니다.


[그림 3]

위 [그림 3]에서 [일반] 탭을 보면 입력할 사항들이 많습니다. 하지만 MS SQL 서버를 연결된 서버로 등록하는 경우는 "연결된 서버" 이름만 입력하고 "서버 유형"에서 "SQL Server" 만 선택하면 됩니다. 다머지 항목들은 MS SQL 서버가 아닌 다른 DBMS와 연결을 하는 경우에 사용됩니다. 이 부분은 나중에 사용을 해보도록 하겠습니다.

저희가 연결된 서버로 등록하려고 하는 것은 FUTURE\INST01 서버입니다. 이 서버를 등록하기 위해 다음 [그림 4] 처럼 입력과 선택을 하면 됩니다.


[그림 4]

이제 [보안] 탭과 [서버 옵션] 탭에 내용을 입력해야 합니다. 우선 [보안] 탭을 살펴 보도록 하겠습니다.


[그림 5]

[그림 5]에서 맨 위의 "로컬 서버 로그인과 원격 서버 로그인 매핑" 부분은 "로컬 로그인"에 지정된 계정으로 로컬 서버로 로그인 해서 연결된 서버에 접속을 하면 "원격 사용자"에 지정된 계정과 "원격 암호"로 지정된 암호를 이용해서 접속한 것으로 가장하라는 설정을 하는 것입니다. 예를 들어 FUTURE 서버에 jangrae 계정이 있고 FUTURE\INST01 서버에 james 라는 계정이 있는 상태에서 jangrae 계정으로 FUTURE 서버에 로그인 한 후 연결된 서버 FUTURE\INST01에 접속하면 james 계정으로 접속 한 것처럼 하고자 하는 경우 다음과 같이 설정하면 됩니다.


[그림 6]

위 [그림 6]에서 "가장" 부분은 원격 서버(연결된 서버 FUTURE\INST01)에 jangrae 라은 계정이 존재 할 경우 체크해 주면 됩니다. [그림 6]의 경우는 로컬 서버(FUTURE)와 원격 서버(FUTURE\INST01)의 계정이 다르기 때문에 계정과 암호를 지정해 준 것입니다.

※ 현재 FUTURE 서버에는 jangrae 계정이 등록되어 있고, FUTURE\INST01 서버에는 james 계정이 등록되어 있는 상태이므로 위 작업이 가능한 것입니다.

[그림 6]에서 지정하는 것은 양쪽 서버(로컬 서버와 원격 서버)의 계정을 1:1 로 지정해주고 있습니다. 하지만 이렇게 계정을 개별적으로 지정하지 않고 연결된 서버를 사용하기 위해서는 아래 부분 "위 목록에서 정의..." 부분을 사용하면 됩니다. 만일 로컬 서버(FUTURE)에 어떤 계정으로 연결되었든지간에 원격 서버(FUTURE\INST01)에 언결된 서버로 연결할 경우 james 계정으로 연결이 되도록 하기 위해서는 다음 [그림 7]과 같이 지정하면 됩니다.


[그림 7]

이렇게 되면 FUTURE 서버에 로그인 하더라도 FUTURE\INST01을 james 계정에 설정된 권한을 이용해 접근해 데이터를 사용 할 수 있습니다. 연결된 서버를 설정할 경우 적절한 권한을 갖고 접근 할 수 있도록 [그림 7]에서 설정을 해주어야 합니다. 만일 [그림 7]에서 FUTURE\INST01의 sa 계정과 암호를 지정하게 되면 FUTURE 서버에 로그인 해서 FUTURE\INST01을 다음대로 접근해 데이터베이스를 사용할 수 있게 됩니다. 이런 방법 보다는 적절한 권한을 갖는 계정을 FUTURE\INST01에 설정하고 그 계정을 이용해 접근이 되도록 지정해야 합니다. [그림 7]에서 지정된 james 계정의 경우 FUTURE\INST01 서버에서 Pubs 데이터베이스에 대한 접근 권한만이 설정되었기 때문에 Pubs 데이터베이스 이외의 데이터베이스에는 접근을 할 수 없게 됩니다.

나머지 설정 부분은 [도움말] 버튼을 눌러 도움말을 참조하여 그 역할을 필히 확인하시기 바랍니다. 아래 [그림 8]은 FUTURE\INST01 서버가 FUTURE 서버에 연결된 서버로 등록되어 테이블 목록이 표시되고 있는 내용입니다.


[그림 8]

위 [그림 8]에서 오른쪽에 표시된 테이블들은 FUTURE\INST01 서버의 Pubs 데이터베이스의 테이블 들입니다. 왜 Pubs 데이터베이스의 테이블이 표시되고 있을 까요?

그 이유는 연결된 서버 설정시 지정된 james 계정의 기본 데이터베이스가 Pubs 이기 때문입니다.

2. 연결된 서버의 사용

이제 FUTURE\INST01을 연결된 서버로 등록하였으므로 FUTURE 서버에 로그인 해서 다음과 같은 쿼리문을 수행하면 원했던 결과가 표시됩니다.

SELECT * FROM [FUTURE\INST01].Pubs.dbo.Titles

연결된 서버의 특정 테이블을 참고하기 위해서는 위 쿼리문에서처럼 "서버이름.데이터베이스이름.소유자.테이블"과 같이 사용해야 합니다. [FUTURE\INST01] 처럼 []를 사용한 이유는 특수문자 \ 가 포함된 인스턴스를 사용하기 때문이며, 일반적인 경우는 []를 사용할 필요가 없습니다.

만일 다음과 같은 쿼리문을 수행하면 결과는 어떨까요?

SELECT * FROM [FUTURE\INST01].master.dbo.sysobjets

다음과 같은 에러가 발생하게 됩니다.

서버: 메시지 7314, 수준 16, 상태 1, 줄 1
OLE DB 공급자 'FUTURE\INST01'에 '"master"."dbo"."sysobjets"' 테이블이 없습니다. 테이블이 존재하지 않거나 현재 사용자에게 해당 테이블에 대한 사용 권한이 없습니다.

왜냐하면 FUTURE\INST01의 james 계정이 master 데이터베이스에 대한 권한이 없기 때문입니다. james 계정은 FUTURE\INST01의 Pubs 데이터베이스에 대한 권한만을 가지고 있습니다. 그렇다면 다음의 쿼리문은 어떻게 될까요?

SELECT * FROM [FUTURE\INST01].Northwind.dbo.Customers

james 계정에 Northwind 데이터베이스에 대한 접근 권한이 없으므로 에러가 나야 하는게 정상적으로 수행됩니다. 그 이유는 FUTURE\INST01 서버의 Northwind 데이터베이스에 guest 계정이 있기 때문입니다. (이 부분에 대해 이해가 안되시는 문은 로그인계정 관련된 강좌를 참고하시기 바랍니다.)

이제 다음과 같은 쿼리문도 가능합니다.

USE Pubs

SELECT S.stor_id, S.title_id, T.title 
FROM Sales S 
INNER JOIN [FUTURE\INST01].Pubs.dbo.Titles T
ON S.title_id = T.title_id

FUTURE 서버의 Pubs의 Sales 테이블과 FUTURE\INST01 서버의 Pubs의 Titles 테이블을 조인해서 검색을 하고 있습니다. 물론 위 예제의 경우 큰 의미는 없습니다. 하지만 실제 업무에서 이러한 기능은 많은 효과를 가져오게 됩니다.

3. QA를 이용한 연결된 서버 만들기

QA를 이용해서 연결된 서버를 만드는 과정은 다음과 같습니다.

USE master
GO

EXEC sp_addlinkedserver 
'FUTURE\INST01', N'SQL Server'
GO

EXEC sp_addlinkedsrvlogin 'FUTURE\INST01', 'false', NULL, 'james', 'password'
GO

위 쿼리문에서 sp_addlinkedsrvlogin 부분은 EM에서 [보안] 탭에서 지정하는 방법이 여러가지인것 처럼 이 부분도 여러가지 방법으로 사용이 됩니다. 만일 FUTURE 서버의 jangrae 계정과 FUTURE\INST01 서버의 james 계정을 매핑 시키는 경우는 다음과 같이 해야 합니다.

USE master
GO

EXEC sp_addlinkedserver 
'FUTURE\INST01', N'SQL Server'
GO

EXEC sp_addlinkedsrvlogin 'FUTURE\INST01', 'false', 'jangrae', 'james', 'james'

앞의 방법과 비교해서 sp_addlinkedsrvlogin 부분이 달라진 것을 볼 수 있습니다. sp_addlinkedsrvlogin 저장프로시져에 대한 자세한 내용은 온라인 설명서를 참고하시기 바랍니다.

4. 정리

이번 강좌에서는 연결된 서버를 만드는 과정을 살펴 보았습니다. 예전에 연결된 서버 만드는 방법을 물어오는 분들이 있었는데 이제야 강좌를 올리게 되었습니다. 너무 늦은 감이 있어 죄송합니다.

이제 연결된 서버를 만드는 방법을 배웠으니 이것을 토대로 해서 다양한 연결된 서버 이용방법을 살펴 보도록 하겠습니다. 예를 든다면 엑셀 파일의 워크시트의 내용을 QA에서 검색하고 입력하는 방법 또는 텍스트 파일의 내용을 QA에서 검색하고 입력하는 방법등입니다. 다음 강좌를 기대해 주시기 바랍니다.


<#출처 SQL World : http://www.sqlworld.pe.kr/mboard/mboard/mboard.asp?board_id=sql02&group_name=board&idx_num=25&page=1&category=&search=&b_cat=0&order_c=idx_num&order_da=desc>


[C#] Server / Client Data 통신[02] - String의 Data를 DataSet으로 변환 by chris

Server / Client Data 통신[01]에서 Server 단의 구현을 알아보았다.

이번에는 Client 단 에서 xml 형식의 string Data 를 DataSet 으로 변환하는 방법 예제를 통해서

알아 보겠다.

 

public DataSet DataSetByStringConverter(string xmlData)

{

// ...... 이전 코드는 생략.

 

var dataSetXml = new DataSet();

 

// xml 문자열 data 를 MemoryStream 객체를 사용하여 읽는다.

var streamXml = new MemoryStream(System.Text.Encoding.Default.GetBytes(xmlData);

 

// stream 으로부터 DataSet 을 읽는다.

dataSetXml.ReadXml(streamXml);

 

// ...... 이하 코드 생략.

}

 

위의 예제의 방법을 이용하면 Xml 형식으로 저장 된 string type 의 data 의 정보를 DataSet 에 담을 수 있다.

 

또, 한 가지의 방법은 XmlDocument 로 읽는 방법이 있다.

 

public DataSet DataSetByStringConverter(string xmlData)

{

// ...... 이전 코드 생략.

 

// XmlDocument 객체 선언.

var xmlDoc = new XmlDocument();

 

// LoadXml Method 를 사용하여 Xml 문자열 읽기.

xmlDoc.LoadXml(xmlData);

 

// ...... 이하 코드 생략.

}

 

지금까지 Server 에서 받은 Xml 형식의 string data 를 DataSet 으로 변환하는 방법을 알아보았다.

위 코드를 응용하여 테스트 프로그램을 만들어 테스트 해보길 바란다.


[C#] Server / Client Data 통신[01] - DataSet 정보를 string으로 변환 by chris

Server 와 Client 간의 Data 통신 관련 작업을 할 때 우리는 DataSet 에 Data 를 담아서 통신하는 방식을 많이 이용하기도 한다.

하지만, DataSet 으로 통신을 하는 방식은 속도 저하의 원인이 될 수 있다.

물론 필자도 같은 경험을 한 적도 있다.

 

한 번은 Server 와 Client 간의 통신 속도가 느려졌다는 이슈가 있어 해결하려고 보니

Server 와 Client 간의 soup 통신을 위해 Server 의 Data 를 DataSet 에 담아 그 것을 통신하고 있는 방식을 사용하고 있었던 것이다.

그래서 지금 소개하는 방식으로 해결을 했던 적이 있었다.

 

아무튼, DataSet 을 Client 에 보내는 것이 아니라 string에 xml 형식으로 Data 를 담아서

Client 단 에서 받은 string 의 xml 형식의 Data 를 DataSet 으로 변환하여 작업하는 것이 효율적이다.

 

밑의 예제를 통해서 주요 구문만 간단히 알아보자.

 

 Public string GetDataString()

{

// ...... 작업에 필요한 코드들이 있을 것이다.

// 생략하고..

 

var dataSet = db.ExecuteDataSet(dbCommand);

var stringWriteData = new StringWriter();

dataSet.WriteXml(stringWriteData, XmlWriteMode.WriteSchema);

 

return stringWriteData.ToString();

}

 

위와 같이 Server 단 에서 DataSet 형식의 Data 를 string 에 xml 형식으로 변환하여 담아내고 있다.

 

자세한 코드는 각자 본인이 테스트 프로그램을 만드면서 위의 구문을 이용하여 Server 단 작업을 해보길 권한다.


[C#] C# Study - 19 : 파일 입출력 by chris

출처 카페 > C# Basic Develo.. | Teacher
원문 http://cafe.naver.com/developer2nd/72

파일 & 디렉토리
- System.IO
- FileInfo
- DirectoryInfo
- FileSystemInfo
- 파일 및 디렉토리 조작 관련 클래스

인코딩
- 문자 코드를 컴퓨터가 이해가능한 0과 1의 바이너리 값을 가지는 연속적인 비트형태로 매핑시켜주는 작업
- ASCII : 7비트사용, 총 128문자 표현
- ISO-8859-1 : 8비트 사용, 서유럽 문자 집합, 기존 ASCII코드에 추가 문자 포함, 총 256문자 표현
- KSC 5601 : 한국 공업표준, 2바이트 완성형 한글 표현, ASCII 문자제외
- EUC-KR : ASCII문자 코드는 1바이트, 한글은 2바이트로 표현
- 유니코드 : 인간이 사용하는 모든 언어표현, 2바이트 사용, 총 65365개 문자표현
- UTF-8 : ASCII문자 코드는 1바이트, 다른 문자는 2바이트나 그 이상으로 표현(한글은 3바이트로 인코딩), 기본 사용 추세
- UTF-16 : 2바이트로 모든 문자 코드 표현

스트림
- C#에서의 모든 입출력
- 스트림을 통해 입출력되는 단위는 byte -> 스트림 내부에는 데이터 타입X -> 어떤 종류의 입출력장치나 파일도 쉽게 처리
- 프로그램상의 객체(데이터)는 데이터 타입이 있기 때문에 이런 바이트 단위와 변한하는 과정이 필요 -> 닷넷 스트림 관련 클래스
- System.IO : 스트림 관련 클래스

Stream 추상클래스
- 모든 스트림
- FileStream, MemoryStream, NetworkStream
- 입출력 장치에 관계없는 일관된 프로그래밍 지원

FileStream
- 파일에 들어있는 데이터를 바이트 배열로 읽고 쓰기 위한 기능 제공

Stream 관련 클래스
- 의미없이 연속된 바이트 데이터를 의미있는 타입 데이터로 바꾸는 기능 제공
- Reader / Writer
- StreamReader : 파일등의 입출력 장치에서 얻은 바이트들을 여러 인코딩을 통해서 의미있는 문자나 문자열로 해석(TextReader 구현)
- StreamWriter : 프로그램에서의 의미있는 문자나 문자열을 입출력 장치로 쓸수 있게 한다(TextWriter 구현)
- 개발자는 더이상 바이트 스트림에 대한 신경 안써도 됨 -> 스트림을 직접 다루지 않아도 된다.

FileStream.Flush
- 스트림에 대한 모든 버퍼를 지우고 버퍼링된 모든 데이터가 내부 장치에 저장되도록 한다.
- Close를 하기전에 적절히 사용

파일 메소드

- 파일에 텍스트 추가 : File.AppendText, FileInfo.AppendText

- 파일이름 변경, 이동 : File.Move,FileInfo.MoveTo

- 파일삭제 : File.Delete, FileInfo.Delete

- 파일복사 : File.Copy, FileInfo.CopyTo

- 파일 크기를 정보 : FileInfo.Length

- 파일특성 가져옴 : File.GetAttributes

- 파일의 특성 설정 : File.SetAttributes

- 파일이 존재여부 체크 : File.Exists

- 파일의 정규화된 경로를 검색 : Path.GetFullPath

- 파일 확장명을 검색 : Path.GetExtension

- 경로에서 파일 이름 및 확장명을 검색 : Path.GetFileName

- 파일 확장명을 변경 : Path.ChangeExtension

* 파일 속성 이용 예제

using System;
using System.IO;

namespace CSharpStudy
{
    class MainClass
    {
        [STAThread]
        static void Main(string[] args)
        {
            string path = @"C:\boot.ini";

            Console.WriteLine("------------- -FileInfo ------------------");

            FileInfo file = new FileInfo(path);


            if(file.Exists) //파일이 해당경로에 존재하는지 여부 체크
            {
                Console.WriteLine("Attributes : {0}", file.Attributes);
                Console.WriteLine("File Name : {0}", file.Name);
                Console.WriteLine("File Ext : {0}", file.Extension);
                Console.WriteLine("File Size : {0}", file.Length);
                Console.WriteLine("Create time : {0}", file.CreationTime);
                Console.WriteLine("DirectoryName : {0}", file.DirectoryName);
                Console.WriteLine("Full Name: {0}", file.FullName);
                Console.WriteLine("Last AccessTime : {0}", file.LastAccessTime.ToString());
                Console.WriteLine("Last WriteTime : {0}", file.LastWriteTime.ToString());
            }
            else
            {
                Console.WriteLine("파일이 존재하지 않습니다.");
            }

            Console.WriteLine("\n");

            Console.WriteLine("-------------- DirectoryInfo ------------------");

            string dirPath = @"D:\Inetpub\wwwroot";
            DirectoryInfo dir = new DirectoryInfo(dirPath);

            Console.WriteLine("Attributes : {0}", dir.Attributes);
            Console.WriteLine("Directory Name : {0}", dir.Name);
            Console.WriteLine("Parent : {0}", dir.Parent.Name);
            Console.WriteLine("Root : {0}", dir.Root.Name);
            Console.WriteLine("Create time : {0}", dir.CreationTime);
            Console.WriteLine("Full Name: {0}", dir.FullName);
            Console.WriteLine("Last AccessTime : {0}", dir.LastAccessTime.ToString());
            Console.WriteLine("Last WriteTime : {0}", dir.LastWriteTime.ToString());

        }
    }
}

*파일 메소드 이용예제

using System;
using System.IO;

namespace CSharpStudy
{
    class MainClass
    {
        [STAThread]
        static void Main(string[] args)
        {
            string path = @"text.txt";
            FileInfo file = new FileInfo(path);
            bool flag = true;

            while(flag)
            {
                Console.WriteLine("=====================");
                Console.WriteLine(" 파일 처리");
                Console.WriteLine("=====================");
                Console.WriteLine("1. 파일생성");
                Console.WriteLine("2. 텍스트추가");
                Console.WriteLine("3. 파일 읽기");
                Console.WriteLine("4. 파일삭제");
                Console.WriteLine("5. 파일 복사");
                Console.WriteLine("6. 파일이동");
                Console.WriteLine("7. 종료");
                Console.Write("\n원하시는 작업 : ");

                string sel = Console.ReadLine();
                switch(sel)
                {
                    case "1":
                        CreateText(file);
                        break;
                    case "2":
                        AppendText(file);
                        break;
                    case "3":
                        ReadText(file);
                        break;
                    case "4":
                        DeleteFile(file);
                        break;
                    case "5":
                        CopyFile(file);
                        break;
                    case "6":
                        MoveFile(file);
                        break;
                    case "7":
                    default:
                        Console.WriteLine("프로그램을 종료합니다.");
                        flag = false;
                        break;
                }
            }
        }

        //파일생성
        public static void CreateText(FileInfo file)
        {
            StreamWriter writer = file.CreateText();
            //UTF-8로 인코딩된 텍스트를 쓰기 위해 파일을 만들거나 엽니다
            //file.CreateText(쓰기용으로 사용할 파일의 경로); -> 경로가 없으면 자기자신에게 실행

            writer.Close();
            Console.WriteLine("파일이 생성되었습니다\n\n");
        }

        //텍스트추가
        public static void AppendText(FileInfo file)
        {
            if(file.Exists)
            {
                Console.WriteLine("\n텍스트를 입력하세요(/p : 입력종료)");
                StreamWriter writer = file.AppendText();
                //기존 파일에 UTF-8로 인코딩된 텍스트를 추가하는 StreamWriter를 만듬
                //file.AppendText(추가하고자하는 파일의 경로); ->경로가 없으면 자기자신에게 실행

                string inputString = Console.ReadLine();
                while(inputString != "/q")
                { 
                    writer.WriteLine(inputString);
                    inputString = Console.ReadLine();
                }

                writer.Close();
                Console.WriteLine("텍스트 추가가 완료 되었습니다.\n\n");
            }
            else
            {
                Console.WriteLine("파일이 존재하지 않습니다.");
            }

        }

        //파일읽기
        public static void ReadText(FileInfo file)
        {
            if(file.Exists)
            { 
                StreamReader reader = file.OpenText();

                //UTF-8로 인코딩된 기존 텍스트 파일을 읽기 용으로 엽니다

                Console.WriteLine(reader.ReadToEnd());
                Console.WriteLine("\n\n");
                reader.Close();
            }
            else
            {
                Console.WriteLine("파일이 존재하지 않습니다\n\n");
            }
        }

        //파일삭제
        public static void DeleteFile(FileInfo file)
        {
            if(file.Exists)
            { 
                file.Delete();
                Console.WriteLine("파일을 삭제하였습니다.");
            }
            else
            {
                Console.WriteLine("파일이 존재하지 않습니다\n\n");
            }
        }

        //파일복사
        public static void CopyFile(FileInfo file)
        {
            if(file.Exists)
            { 
                file.CopyTo("copy.txt", true);
                Console.WriteLine("파일을 복사하였습니다.");
            }
            else
            {
                Console.WriteLine("파일이 존재하지 않습니다\n\n");
            }
        }

        //파일이동
        public static void MoveFile(FileInfo file)
        {
            if(file.Exists)
            { 
                Console.Write("이동 경로 입력 : ");
                string dirPath = Console.ReadLine();
                file.MoveTo(dirPath + "\\move.txt"); //해당경로로 이름을 바꿔서 이동한다.
                Console.WriteLine("파일을 복사하였습니다.");
            }
            else
            {
                Console.WriteLine("파일이 존재하지 않습니다\n\n");
            }
        }

    }
}


[C#] C# Study - 18 : 델리케이트와 이벤트 by chris

출처 카페 > C# Basic Develo.. | Teacher
원문 http://cafe.naver.com/developer2nd/71

위임과 이벤트(Delegate & Event)
- 메소드의 실행을 위임 변수가 대행
- 메소드 포인터
- 위임형식은 동일한 파라미터와 반환형을 가지는 여러개의 메소드를 수행 가능하다.
- 객체 메소드 or 정적메소드 둘다 가능
- 위임은 메소드 포인터만 저장, 코드 기술은 불가
- 인터페이스를 지원하는 객체들 내에서 동일한 메소드 명으로 객체 내의 메소드 수행이 가능하듯이 위임도 동일한 위임명으로 메소드 형식이 동일한 다른 메소드 실행이 가능(다형성)

위임선언
- public delegate 반환형 위임명(인자리스트);
- delegate 키워드(본문X)
- 메소드 파라미터 형식과 메소드 반환형식이 같으면 위임 형식에 위임 가능

publid delegate void Sample (int x, int y);
- public static void M1(int x, int y); //가능
- public void M2(int x, int y) //가능
- public int M3(int x, int y); //X, 리턴타입이 다름
- public void M4(int x); //X, 인자갯수가 다름
- public void M5(string x, int y); //X, 인자의 타입이 다름
- public void M6(int x, int y, int z); //X, 인자수가 다름

위임생성
- System.Delegate와 System.MulticastDelegate로 부터 상속
- 따라서 new 연산자로 위임 객체 생성
- 생성자의 인자는 반드시 정적메소드면 "클래스명.메소드명", 객체메소드면 "객체명.메소드명"

Sample d = new Sample(Class.M1); //인자에 메소드명만 들어간다.
Sample d = new Sample(obj.M2);

using System;

namespace CSharpStudy
{
    class MyClass
    {
        public int num = 0;
        public void Plus(int value)
        {
            this.num +=value;
        }
        public void Minus(int value)
        {
            this.num -=value;
        }
        public static void PrintHello(int value)
        {
            for(int i=0; i<value; i++)
                Console.WriteLine("Hello~");
        }
    }

    //Delagete선언
    public delegate void Sample(int value);

    class MainClass
    {
        [STAThread]
        static void Main(string[] args)
        {
            MyClass c = new MyClass();
            Sample d = new Sample(c.Plus);

            //위임
            d(10);
            Console.WriteLine(c.num);

            c.Plus(10);
            Console.WriteLine(c.num);

            d = new Sample(c.Minus);
            d(10);
            Console.WriteLine(c.num);

            c.Minus(10);
            Console.WriteLine(c.num);

            //정적메소드 위임
            d = new Sample(MyClass.PrintHello);
            d(5);
    
        }
    }
}

위임사용
- 위임 객체 사용은 일반메소드와 동일
- 위임 호출시 파라미터를 전달하면 실제 메소드에 파라미터 전달, 호출
- 동일한 위임객체로 어떤 메소드를 참조하느냐에 따라 호출변경(메소드 다형성)
- 위임자는 변경할 수 없으며 일단 만들어지면 위임자의 호출 목록은 바뀌지 않는다.
- 여러 개의 호출목록을 지정해 주려면 그 Delegate의 형식은 리턴 타입이 반드시 void 이어야 함.
-> 매개변수중에 어느것도 out 이 지정되어선 안된다.


위임연산
- Combine(+), Remove(-)
- 위임 객체에 메소드 추가, 제거
- 위임 객체가 가지는 메소드 리스트(Invocation List)

Combine & Remove
- Combine과 Remove를 명시적으로 호출해서 리스트 관리 기능

위임간 비교연산
- Equals, ==, != 재정의
- Invocation List를 비교한다.

using System;
namespace CSharpStudy
{
    // Mult Delegate 사용에 관한 예제입니다.

    class MyClass
    {
        public int num = 0;
        public void Plus(int value)
        {
            this.num +=value;
        }
        public void Minus(int value)
        {
            this.num -=value;
        }
        public static void PrintHello(int value)
        {
            for(int i=0; i<value; i++)
                Console.WriteLine("Hello~");
        }
    }

    //delegate 선언
    public delegate void Sample(int value);

    // 다중 메소드 연산이 가능
    // 멀티 Delegate를 사용할 때 메서드는 반드시 void를 반환해야 함 -> 호출을 목적으로 하기때문
    // void를 반환하지 않으면 +=과 같은 연산자를 사용할수 없다.
    // 중복 메서드를 포함 가능
    //호출된 메서드가 예외를 throw하면 메서드는 실행을 멈추고 예외는 대리자의 호출자에게 전달.
    //호출 목록에 남아있는 메서드는 호출되지 않음. 호출자 안에서 예외를 catch해도 이 동작은 변하지 않습니다.

    class MainClass
    {
        [STAThread]
        static void Main(string[] args)
        {
            //위임 메소드 리스트 관리
            MyClass c = new MyClass();
            Sample d;

            //Delegate Combine
            // d = (Sample)Delegate.Combine(new Sample(c.Plus), new Sample(c.Minus));
            d = new Sample(c.Plus) + new Sample(c.Minus); // +연산자 오버로딩됨
            d(10); //c.Plus와 c.Minus 메소드가 순서대로 실행됨. 결과값 : 0
            Console.WriteLine(c.num);

            //Delegate Remove
            // d = d - new Sample(c.Minus); //c.Minus메소를 제거 
            d -= (Sample) new Sample(c.Minus); // -=연산자 오버로딩됨
            d(10); //c.Plus메소드만 실행. 결과값: 10
            Console.WriteLine(c.num);

            //Delegate Combine
            d += new Sample(MyClass.PrintHello);

            //MyClass.PrintHello 메소드가 추가, +=연산자 오버로딩됨
            d(5); //c.Plus와 MyClass.PrintHello 메소드 실행, 오래등록된 메소드부터 실행
            Console.WriteLine(c.num);

            //Deleate 비교연산 -> 가지고 있는 메소드 목록(목록 순서, 목록갯수 포함)을 비교한다.
            Sample s1 = new Sample(c.Plus) + new Sample(c.Minus);
            Sample s2 = new Sample(c.Plus);

            if(s1 == s2)
                Console.WriteLine("s1과 s2가 같습니다");
            else
                Console.WriteLine("s1과 s2가 다릅니다");
            // 가지고 있는 목록이 다르므로 다르다는 내용 출력

            s2 += new Sample(c.Minus);

            if(s1 == s2)
                Console.WriteLine("s1과 s2가 같습니다");
            else
                Console.WriteLine("s1과 s2가 다릅니다");
            // 가지고 있는 목록이 같으므로 같다는 내용 출력


        }
    }
}

이벤트(Event)
- 동작을 수행하는 방식
- 출판자(Publisher) : 이벤트를 발생하며 특정 구독자에게 이벤트 발생을 알려준다.
- 구독자(Subscriber) : 특정 이벤트 발생 통보를 받고 출판자로부터 호출되어질 메소드를 등록한

객체
- 이벤트 핸들러(Event Handler) : 호출되어지는 메소드
- 구독자들이 출판자에게 자신의 이벤트 핸들러를 출판자의 이벤트 핸들러 목록에 등록한다.

이벤트 정의
- event 키워드
- 접근지정자 event Delegate명 이벤트명;
- 반드시 위임과 같이 정의한다.

이벤트 핸들러 추가 및 해제
- +=, -+로 이벤트 핸들러를 출판자 객체에게 등록 및 제거한다.
- 위임 등록과 동일
- 객체명.이벤트명 += new Delegate명(객체명.메소드명);

구독자들에게 이벤트 발생 알리기
- 위임에서 위임 객체를 실행하며 Invocation List안에 모든 메소드를 실행하듯이 이벤트 요청이 발생하면 이벤트안에 이벤트 핸들러가 있는지 조사하고 이벤트 핸들러를 호출한다.

이벤트 핸들러 생성
- 모든 형태가능 -> 단 위임 형식과 동일한 메소드 형식
- 보통 포준적인 이벤트 핸들러 형식
public delegate void EventHandler(object sender, EventArgs args)


- object sender : 동일한 이벤트 핸들러가 다중의 출판자에게 등록이 가능하므로 sender를 가지고 호출자(출판자)를 구별
- EventArgs args : 이벤트 발생시 추가 정보 전달
- 모든 이벤트 핸들러는 이 위임형과 반드시 동일

using System;

namespace CSharpStudy
{
    class Button
    {
        public event ButtonClick Click;//이벤트생성, 접근지정자 event Delegate명 이벤트명;
        public string Text;

        public void Trigger()
        {
            ButtonClickEventArgs e = new ButtonClickEventArgs();
            e.i = 100;
            Console.WriteLine("Button이 클릭되었습니다");
            Click(this, e); //실제로 이벤트를 발생시키는 부분
        }
    }

    //delegate
    public delegate void ButtonClick(object sender, ButtonClickEventArgs e);

    //사용자 정의 EventArgs
    public class ButtonClickEventArgs : EventArgs
    {
        public int i;
    }

    class MainClass
    {

        //이벤트 핸들러 선언
        public static void button1_Click(object sender, ButtonClickEventArgs e)
        {
            //object sender는 메시지가 어디서 발생하는지 발생된 곳의 참조값
            // ButtonClickEventArgs e는 이벤트의 데이터를 담고 있는 매개변수.
            //이 두가지의 매개변수는 바꿀 수 없으며 항상 위와 같은 규칙으로 사용해야 함.

            Button button = (Button) sender;
            Console.WriteLine("Button Text : " + button.Text);
            Console.WriteLine("EventArgs : " + e.i);
            Console.WriteLine("ButtonClick 이벤트가 발생해서 이벤트 핸들러가 호출되었습니다.");
        }

        public static void button1_Click2(object sender, ButtonClickEventArgs e)
        {
            Console.WriteLine("Click2~");
        }

        [STAThread]
        static void Main(string[] args)
        {
            Button btn1 = new Button();
            btn1.Text = "테스트 버튼";
            btn1.Click += new ButtonClick(button1_Click);

            //ButtonClick(위임자)에 호출메서드 등록
            btn1.Click += new ButtonClick(button1_Click2);

            btn1.Trigger(); //이벤트 발생 에뮬레이터

            Console.Read(); //실행화면 일시중지 
        }
    }
}


1 2 3 4 5 6 7 8


통계 위젯 (화이트)

711
42
1252

이 이글루를 링크한 사람 (화이트)

0