개발팁2016. 8. 8. 08:21

C# 의 데이터에는 Value Type 와 Reference Type 이 존재한다.

 

Value Type 은 그 자체가 값인 경우이다.
Reference Type 은 별도의 메모리 공간에 인스탄스가 생성이 되고,
메모리의 위치만을 값으로 가지는 경우다.

 

배열이나 클래스의 객체는 Reference Type 으로, 실제값은 별도로 저장이된다.

 

Class 안에 멤버가 모두 Value Type 이라면, 단순히 Clone 을 통해서, 멤버 값이 전부 새로운 값으로 복사가 된다.
그러나, 한 Class 안에 Value Type, 배열, 클래스 객체가 공존한다면,
이 객체를 Clone 을 하게 되면, Value Type 은 정상적으로 복사가 되지만, 나머지 값은 원래 객체의 주소값이 복사가 된다.

 

원래 객체 / 클론 객체가 모두 동일한 메모리 위치를 가리키기 때문에, 한쪽에서 멤버객체의 값을 변경한다면,
다른곳에서는 의도치 않게 값이 변경되는 결과를 보게 된다.

 

Deep Clone 이라는 것은 Value / Reference Type 모두 새로운 값으로 복사를 하는 것이다.

 

 

2가지 방법이 있는데, 복사하려는 객체가 Serializable 객체라면,
// 2. Serializable 객체에 대한  Deep Clone 을 이용하여,
MemoryStream 을 통해 Serialize 하여 복사가 된다.

 

그렇지 않은 Class 는,
// 1. Deep Clone 구현 을 이용하여, 복사를 하면 된다.

Class 안의 멤버는 Value Type 과 Reference Type 이 있고,
Reference Type 객체 안에는 또다시, Value Type 과 Reference Type 의 멤버가 있고,
그 안에 있는 Reference 에는 또다시 Value Type 과 Reference Type 있다.
이런식으로 계층간에 여러 단계의 객체가 존재할 수가 있다.
이를 모두 복사해야 하기 때문에, Circular 또는 Recursive 를 통해서 값을 복사를 해야 한다.

 

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.Serialization.bformatters.Binary;
using System.IO;

namespace Hungry.Developer
{



    // Copyright 헝그리개발자(https://bemeal2.tistory.com)
    // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

    // C# 객체를 복사해주는 클래스
    public class ObjectCopy
    {
        // 1. Deep Clone 구현
        public static T DeepClone<T>(T obj)
        {
            if (obj == null)
                throw new ArgumentNullException("Object cannot be null.");


            return (T)Process(obj, new Dictionary<object, object>() { });
        }



        private static object Process(object obj, Dictionary<object, object> circular)
        {
            if (obj == null)
                return null;



            Type type = obj.GetType();


            if (type.IsValueType || type == typeof(string))
            {
                return obj;
            }



            if (type.IsArray)
            {
                if (circular.ContainsKey(obj))
                    return circular[obj];



                string typeNoArray = type.FullName.Replace("[]", string.Empty);
                Type elementType = Type.GetType(typeNoArray + ", " + type.Assembly.FullName);
                var array = obj as Array;
                Array arrCopied = Array.CreateInstance(elementType, array.Length);

                circular[obj] = arrCopied;


                for (int i = 0; i < array.Length; i++)
                {
                    object element = array.GetValue(i);
                    object objCopy = null;



                    if (element != null && circular.ContainsKey(element))
                        objCopy = circular[element];
                    else
                        objCopy = Process(element, circular);



                    arrCopied.SetValue(objCopy, i);
                }



                return Convert.ChangeType(arrCopied, obj.GetType());
            }



            if (type.IsClass)
            {
                if (circular.ContainsKey(obj))
                    return circular[obj];



                object objValue = Activator.CreateInstance(obj.GetType());
                circular[obj] = objValue;
                FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);



                foreach (FieldInfo field in fields)
                {
                    object fieldValue = field.GetValue(obj);

                    if (fieldValue == null)
                        continue;



                    object objCopy = circular.ContainsKey(fieldValue) ? circular[fieldValue] : Process(fieldValue, circular);
                    field.SetValue(objValue, objCopy);
                }

                return objValue;
            }
            else
                throw new ArgumentException("Unknown type");
        }
 

        // 2. Serializable 객체에 대한  Deep Clone
        public static T SerializableDeepClone<T>(T obj)
        {
            using (var ms = new MemoryStream())
            {
                var bformatter = new Binarybformatter();
                bformatter.Serialize(ms, obj);
                ms.Position = 0;

                return (T) bformatter.Deserialize(ms);
            }
        }



    }

}


 

 

 

Posted by 헝개
개발팁2016. 8. 2. 07:59

OOP 디자인 패턴중에 Singleton 이라는 것이 있다.

 

일반적으로 클래스는 new 키워드를 통해서, 인스턴스를 생성하지만,
Singleton 클래스는 인스턴스가 1개만 생성이 된다.

 

생성자 즉, Constructor 를 private 으로 선언한다.
이렇게 되면, 외부에서 new 키워드로 인스턴스를 생성할 수 없다.
클래스 내부에서만 new 키워드를 사용하여, 자기 자신의 인스턴스를 생성이 가능하다.

 

아래 클래스 선언 방법대로, getInstance 를 static 으로 선언하고,
자기 자신의 인스턴스를 돌려주게 되면, 단 1개의 인스턴스만 생성하여 사용하게 된다.

 

public class MyClass
{
 private MyClass() { }

 private static MyClass instance;

 public static MyClass getInstance()
 {
  if(instance == null)
   instance = new MyClass();

  return instance;
 }

 public int Method1()
 {
  return 1;
 }
}

 

 

사용시에는 아래와 같이 사용한다.

 

MyClass myc = MyClass.getInstance();
int n = myc.Method1();

Posted by 헝개
개발팁2016. 7. 29. 10:01

IIS 웹서버로 요청되는 모든 웹페이지/웹서비스 요청에 대해, 퍼포먼스 로그를 남기는 방법이다.

 

퍼포먼스를 체크하는 방법은, 시작시간을 기록하고,

웹페이지나 특정 메서드가 실행되고 나서, 종료시간과의 차이를 계산해서 기록을 하면 된다.

 

DateTime _startTime = DateTime.Now;

 

// 웹페이지 / 메서드 실행.. 코드

 

TimeSpan span = DateTime.Now - _startTime;
string perfLog = span.Milliseconds.ToString() + " ms";


 

간단한 코드지만, 이를 매번 웹페이지마다 삽입하여 사용하는것은 어려우니,

HttpModule 을 이용하여, 자동으로 기록하는 방법이 있다.

 

IHttpModule 이라는 인터페이스를 이용하여, WebServiceAuthenticationModule 을 만들것이다.

이는 원래, 인증 체크를 위한 클래스인데, 이 안에, 퍼포먼스 로그 기능을 추가한 것이다.

 

별도의 프로젝트를 만들고, WebServiceAuthenticationModule  클래스를 만들어서,

Application_BeginRequest 와 Application_EndRequest 이벤트를 구현할 것이다.

이 프로젝트를 빌드해서, 별도의 DLL 을 만들면 된다.

 

ASP.NET 프로젝트에서 해당 DLL 을 참조 추가를 하고, Web.Config 에 아래와 같이 모듈을 추가 하면된다.

 

    <system.webServer>
      <!-- 인증체크 설정 -->
      <modules>
        <add  name="WebServiceAuthenticationModule"
              type="Hungry.Developer.HttpModule.WebServiceAuthenticationModule"/>
      </modules>
      ...

    </system.webServer>


 

이렇게 되면, 해당 ASP.NET  웹서비스의 모든 웹페이지는

항상 페이지 요청시, WebServiceAuthenticationModule 의 Application_BeginRequest 가 먼저 실행이 될것이고,

요청이 완료되면, Application_EndRequest 를 실행하게 될것이다.

 

이를 이용하여, 맨처음 보여줬던, 퍼포먼스 로그를 기록하면 된다.

 

// Copyright 헝그리개발자(https://bemeal2.tistory.com)
// 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.
public sealed class WebServiceAuthenticationModule : IHttpModule
{
        private DateTime _startTime;    // 퍼포먼스 로깅을 위한 시작시간 저장 변수

        public void Init(HttpApplication app)
        {
            app.BeginRequest +=
                (new EventHandler(this.Application_BeginRequest));
            app.EndRequest +=
                (new EventHandler(this.Application_EndRequest));

        }
        private void Application_BeginRequest(object source, EventArgs e)
        {
             // 웹페이지 / 웹서비스 실행 시작시간 기록
            _startTime = DateTime.Now;

             // 인증체크
             ...
        }

        // 요청완료시, 퍼포먼스 로그 기록
        private void Application_EndRequest(object source, EventArgs e)
        {
            HttpApplication application = (HttpApplication)source;

            TimeSpan span = DateTime.Now - _startTime;
            string perfLog = application.Request.Url + " / " + span.Milliseconds.ToString() + " ms";
            LogWriter.PerfLog(perfLog);
        }

}

 

LogWriter 는 string 값을 파일에 추가하는 클래스다.

파일에 저장을 하던, DB에 저장을 하던, 원하는 방식으로 구현하면 된다.

 

아래는 위의 코드를 그대로 이용한, 실제 로그파일의 내용이다. 이중에 Performance 만 보면 된다.

 

2016-07-29 09:27:37.036 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 100 ms
2016-07-29 09:27:37.359 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(149) / permission(1)
2016-07-29 09:27:37.361 (Debug) From< HttpModule > :권한체크 : 0 / menuid(149) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(ReadBy) / userIP(127.0.0.1)
2016-07-29 09:27:37.430 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(149) / permission(1)
2016-07-29 09:27:37.433 (Debug) From< HttpModule > :권한체크 : 0 / menuid(149) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(ReadBy) / userIP(127.0.0.1)
2016-07-29 09:27:37.470 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 113 ms
2016-07-29 09:27:37.514 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / ReadBy / 87 ms
2016-07-29 09:27:46.580 (Debug) From< HttpModule > :인증체크 : 0 / cachekey(cUwze7r0rMTQCVHW9eXE/A==) / authkey(khMBxeLsajUjv7zdBcnrdG33JUQahNjqCqNzmH5G6nk=) / menuid(139) / permission(1)
2016-07-29 09:27:46.589 (Debug) From< HttpModule > :권한체크 : 0 / menuid(139) / permission(1) / requestURL(http://삐~/WebService/삐~Service.asmx) / webMethodName(삐~) / userIP(127.0.0.1)
2016-07-29 09:27:46.893 (Performance) From< HttpModule > :http://삐~/WebService/삐~Service.asmx / SponsorSearch / 315 ms 

 

Posted by 헝개
개발팁2016. 7. 26. 09:46

C# 어플리케이션, 보통 winform 형태의 어플리케이션에서
캐쉬를 만들고 처리하는 방법이다.

 

캐쉬는 데이터를 저장하고 꺼내쓰기 위해서 사용을 하는데,
데이터를 읽기 위해서 매번, 저장소에서 꺼내오는 것이 아니라,
저장소에서 읽어서 캐쉬에 저장을 해놓고, 캐쉬에서 꺼내쓴다면,
속도 향상과, 저장소에 대한 트래픽을 줄일수 있어 효율적으로 사용이 가능하다.

 

아래와 같은 방법으로 저장소에 대해서 자료를 가져오는 클래스를 정의하여 사용하면,
데이터를 가져다 쓰는 객체는 내부 구조에 상관없이 동일한 인터페이스로 사용할 수 있을것이다.


자료 클래스


- 자료읽기 요청
  1. 캐쉬에서 자료 읽기
     데이터가 없으면, 저장소에서 읽어옴
     캐쉬에 자료 저장
  2. 읽어온 자료 리턴

 

- 자료쓰기 요청
  1. 저장소에 자료 쓰기
  2. 캐쉬 삭제

 


저장소는 외부에서 변경되지 않는다는 보장이 되어야 한다.
외부에서 변경된다면, 캐쉬데이터와 저장소데이터간에 차이가 발생하게 된다.

실제로 Application Cache Object 를 아래와 같이 구현하였다.


 

    // Application 캐쉬 객체
    class AppCacheObject
    {
        private static HttpRuntime _httpRuntime;

        public static Cache Cache
        {
            get
            {
                EnsureHttpRuntime();
                return HttpRuntime.Cache;
            }
        }

        private static void EnsureHttpRuntime()
        {
            if (null == _httpRuntime)
            {
                try
                {
                    Monitor.Enter(typeof(AppCacheObject));
                    if (null == _httpRuntime)
                    {
                        _httpRuntime = new HttpRuntime();
                    }
                }
                finally
                {
                    Monitor.Exit(typeof(AppCacheObject));
                }
            }
        }

    } 

 

Cache 는 System.Web.Caching.Cache 의 객체이며, Get Property 로 사용한다.
System.Web 을 사용하기 때문에, System.Web.HttpRuntime 의 Cache 객체로 생성한다.

 

이는 Cache 객체에 대한 정의이며, 이를 이용하여,
캐쉬를 저장하는 SetCache
캐쉬를 조회하는 GetCache
캐쉬를 삭제하는 RemoveCache

 

여기서 한가지 중요한 것이, 캐쉬종류이다.
Absolute Expiration 과 Sliding Expiration 이라는것이 있다.

 

Absolute Expiration 은 지정된 만료시간이 지나면, 무조건 캐쉬가 삭제되는 것이고,
Sliding Expiration 은 동일하게 지정된 만료시간에 캐쉬가 삭제되지만, GetCache 를 할때마다 자동으로 만료시간이 연장이 된다.

 

일반적으로 로그인 처리를 하여, 로그인정보를 저장할때 Sliding Expiration Cache 를 사용하면,
지정된 시간이 지나면, 자동로그아웃이 되지만, 계속 작업을 한다면 (작업시마다 캐쉬를 사용하여 로그인정보를 확인) 로그인이 풀리지 않게 되는것이다.

 

실제로, AppCacheManager 라는 클래스로,
SetCache / GetCache / RemoveCache 와 슬라이딩 캐쉬를 구현한 것이다.
슬라딩캐쉬는 SetCache 에서만 지정해주면, GetCache / RemoveCache 는 동일하게 사용된다.

 

    // Copyright 헝그리개발자(https://bemeal2.tistory.com)
    // 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

    // Application 캐쉬
    public class AppCacheManager
    {
        // 인증용 캐쉬 저장 : 슬라이딩 만료 캐쉬 - minute 후에 만료되지만, GetCache 요청이 있으면, 만료시간이 연장됨
        public static bool SetSlidingCache(string name, object value, int minute)
        {
            bool bvalue = true;

            try
            {
                AppCacheObject.Cache.Add(name, value, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(minute), CacheItemPriority.Normal, null);
            }
            catch
            {
                bvalue = false;
            }

            return bvalue;
        }

        // 캐쉬 저장 : 절대 시간 만료 캐쉬 - minute 후에 만료
        public static bool SetCache(string name, object value, int minute)
        {
            bool bvalue = true;

            try
            {
                AppCacheObject.Cache.Add(name, value, null, DateTime.Now.AddMinutes(minute), System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
            }
            catch
            {
                bvalue = false;
            }

            return bvalue;
        }

        // 캐쉬 get
        public static Object GetCache(string name)
        {
            Object ovalue = new Object();

            try
            {
                ovalue = AppCacheObject.Cache.Get(name);
            }
            catch { }

            return ovalue;
        }

        // 캐쉬 제거
        public static Object RemoveCache(string name)
        {
            Object ovalue = new Object();

            try
            {
                ovalue = AppCacheObject.Cache.Remove(name);
            }
            catch { }

            return ovalue;
        }

    } 

 

 

 

 

이를 이용하여, AppCacheExample 을 실행해보면,
로그인정보를 저장하는 Sliding Cache 와 일반 정보를 저장하는 Absolute Cache 를 사용하고 있다.
각각 캐쉬 만료시간을 1분으로 하여,
GetCache 를 했을때, 만료되는지와 자동연장되는지를 확인할 수 있다.

 

 

 

 

AppCacheExample.zip
다운로드

 

Posted by 헝개
개발팁2016. 7. 22. 00:30

주민번호는 AAAAAA-XBBBBBY
- 를 빼면 숫자 13자리이다.

 

앞에 6자리는 생년월일이고,
뒤에 7자리중에 첫번째가 구분값이다.


이값이 5,6,7,8 이면, 재외국인이며, 그외에는 내국인이다.


맨뒤 1자리는 오류검증값이 된다.

 

유효성 검증로직은

13번째 오류검증값을 제외하고, 12개의 숫자를


234567892345
이 숫자와 각 자리수별로 곱한다음 sum 을 한다.


이 값을 11로 나눈 나머지를 11에서 빼고, 맨뒤 1의 자리(0~9)만 취한다.
이 값에 다시 2를 더한다음, 맨뒤 1의 자리(0~9)만 취한다.
이 값이 오류검증값이 되는 것이다.

 

// Copyright 헝그리개발자​(http://bemeal2.tistory.com)
// 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

// 재외국인 주민번호 유효성 검사
private static bool IsValidForeignRegNo(string reg_no)
{
    int sum = 0;
    int odd = 0;

    int[] arrBuffer = new int[13];


    for(int i=0; i < 13; i++)
    {
         arrBuffer[i] = Convert.ToInt32(reg_no[i].ToString());
    }



    odd = arrBuffer[7]*10 + arrBuffer[8];


    if(odd % 2 != 0)
         return false;



    int[] multipliers = {2,3,4,5,6,7,8,9,2,3,4,5};


    for(int i=0; i < 12; i++)
    {
         sum += (arrBuffer[i] *= multipliers[i]);
    }


    sum = 11 - (sum % 11);



    if(sum >= 10)
         sum -= 10;



    sum += 2;


    if(sum >= 10)
         sum -= 10;



    return (sum == arrBuffer[12]);


}

 

코드는 C# 으로 작성되었으나, 코드가 아주 간단하여, 사용하는 언어로 포팅하여 사용하면 된다.

 

Posted by 헝개
개발팁2016. 7. 21. 09:54

사업자 등록번호는 AAA-BB-CCCCY 의 구조이며,
- 를 제외하면, 숫자로 10자리이다.

 

앞의 세자리는 국세청과 세무서별 코드
가운데 두자리는 개인과 법인
뒤에 5자리수중에 앞쪽 4자리는 일련번호
맨 끝자리 1개는 오류검증값이다.

 

유효성 검증로직은

오류검증값을 제외한 9자리를


137137135
이 숫자와 각 자리수별로 곱하여 sum 을 한다.


여기에, 9번째 자리 * 5 / 10 을 한 값을 더하고,
맨뒤 1의 자리 숫자(0~9)만을 취하여,
10 - 맨 마지막값 을 한것이 오류검증값이 되는 것이다.


 

// Copyright 헝그리개발자​(http://bemeal2.tistory.com)
// 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

// 사업자등록번호 유효성 검사
public static bool IsValidBizNo(string biz_no)
{
    biz_no = biz_no.Replace(" ", ""); //공백 제거
    biz_no = biz_no.Replace("-", ""); // 문자 '-' 제거



    if (biz_no.Length != 10) //사업자 등록번호가 10자리인가?
    {
         return false;
    }



    int sum = 0;
    int checknum = 0;
    int[] arrNumList = new int[10];
    int[] arrCheckNum = {1,3,7,1,3,7,1,3,5};



    for (int i = 0; i < 10; i++)
    {
         arrNumList[i] = Convert.ToInt32(biz_no[i].ToString());
    }



    for (int i = 0; i < 9; i++)
    {
         sum += arrNumList[i] * arrCheckNum[i];
    }



    sum += ((arrNumList[8] * 5) / 10);
    checknum = (10 - sum % 10) % 10;



    return (checknum == arrNumList[9]);

 

코드는 C# 으로 작성되었으나, 코드가 아주 간단하여, 사용하는 언어로 포팅하여 사용하면 된다.

 

Posted by 헝개
개발팁2016. 7. 20. 09:41

ASP.NET 의 View 를 ViewEngine 으로 렌더링해서 나온 HTML 을 엑셀로 내려받는 방법이다.

 

아래와 같이 2개의 정적 메서드를 정적 클래스 (static class) 안에 정의한다.

 

// Copyright 헝그리개발자​(http://bemeal2.tistory.com)
// 소스는 자유롭게 사용가능합니다. Copyright 는 삭제하지 마세요.

// HTML 을 엑셀파일로 웹 다운로드
public static void ToExcelDownload(this string strHTML, string filename)
{
    filename = filename.Replace(" ", "_");



    HttpContext.Current.Response.AppendHeader("content-disposition", "attachment;filename=" + HttpUtility.UrlEncode(filename));
    HttpContext.Current.Response.Charset = "";
    HttpContext.Current.Response.Cache.SetCacheability(HttpCacheability.NoCache);
    HttpContext.Current.Response.ContentType = "application/vnd.ms-excel";
    HttpContext.Current.Response.Write(strHTML);
    HttpContext.Current.Response.End();
}



// View 를 HTML 문자열로 변환
public static string ViewToString(this ControllerContext controller, string viewName, ViewDataDictionary ViewData, TempDataDictionary TempData, object model)
{
    ViewData.Model = model;
    using (var sw = new StringWriter())
    {
         var viewResult = ViewEngines.Engines.FindPartialView(controller, viewName);
         var viewContext = new ViewContext(controller, viewResult.View, ViewData, TempData, sw);
         viewResult.View.Render(viewContext, sw);
         viewResult.ViewEngine.ReleaseView(controller, viewResult.View);


         return sw.GetStringBuilder().ToString();
    }
}

 

ToExcelDownload 메서드는 문자열(html)을 엑셀타입으로 내려받도록 해준다.

실제 내용은 table 테그로 구성된 html 이지만, 엑셀 형식으로 다운로드하도록 해주는 것이다.

 

ViewToString 메서드는 ViewContext 로, View 를 렌더링해서 String 으로 만들어준다.

 

 

엑셀 다운로드 버튼에 대한 Controller 를 아래와 같이 정의할 수 있다.

Model 을 구성하여, ViewToString 으로 View 이름과 Model 객체를 전달하여 준다.

예제에서는 Controller 이름과 동일한 이름의 View 이름을 사용하였다.

 

이 말은, 현재 Controller 의 View 를 호출하여 렌더링 한다는 뜻이다.

물론, 다른 Controller 의 View 이름을 넣어주어도 상관이 없다. 해당 View 에 맞는 Model 만 생성해서 전달해주면 된다.

 

 // 엑셀 다운로드에 대한 컨트롤러
public ActionResult Sample_ExcelDownload(int key_id)
{
    MySampleDataContainer model = new MySampleDataContainer();



    BizSample biz = new BizSample();
    model.header = biz.SampleDataInfo(key_id);
    model.list = biz.SampleDataList(key_id);



    string strHTML = this.ControllerContext.ViewToString("Sample_ExcelDownload", this.ViewData, this.TempData, model);
    string filename = "excel_download_sample.xls";
    strHTML.ToExcelDownload(filename);

    return View();
}

 

 

View 내용인데, 일반적으로 사용하는 View 와 동일하다.

다만 Excel 파일로 열어줘야 하기 때문에 엑셀에서 인식하도록 몇가지 태그가 추가되었을 뿐이다.

 

 // 엑셀 내용에 대한 뷰 - Sample_ExcelDownload
@model HD.DataFactory.MySampleDataContainer
@{
    ViewBag.Title = "Excel";
    Layout = "~/Views/Shared/_ContentLayout.cshtml";
}

<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns="http://www.w3.org/TR/REC-html40">
<head>
    <meta content="text/html; charset=utf-8" http-equiv="content-type" />
    <!--[if gte mso 9]>
<xml>
<x:ExcelWorkbook>
<x:ExcelWorksheets>
<x:ExcelWorksheet>
<x:Name>sheet name</x:Name>
<x:WorksheetOptions>
<x:DisplayGridlines/>
</x:WorksheetOptions>
</x:ExcelWorksheet>
</x:ExcelWorksheets>
</x:ExcelWorkbook>
</xml>
<![endif]-->
</head>
<body>

<table class="doc_data_list" border="1">
...
</table>

</body>
</html>

 

 

Posted by 헝개
개발팁2016. 7. 19. 11:14

Extention Method C# 3.0 에서 추가된 기능이다.

 

Extention Method 를 정의하는 방법은

 

1. System.Linq 를 사용하겠다고 선언한다.
2. static 클래스와 static 메서드로 정의해야 한다.
3. 첫번째 파라미터에 this 라는 키워드를 사용한다.


 

using System.Linq;

namespace test
{
 public static class Extention
 {
  public static string ToUpperCase(this string value)
  {
   // to do...

   return outString;
  }
  public static string AddString(this string value, string extraValue)
  {
   // to do...

   return outString;
  }
 }
}

 

사용방법

 

string str1 = "abcd efgh";
string str2 = "001002003";

 

// 기존 메서드 호출방법
string outStr1 = Extention.ToUpperCase(str1);
string outStr2 = Extention.AddString(str1, str2);

 

// 확장 메서드 호출방법
string extStr1 = str1.ToUpperCase();
string extStr2 = str1.AddString(str2);

 

 

기존 메서드 호출방법과 확장 메서드 호출방법을 모두 다 사용 가능하다.

 

 

 

 

확장메서드는 Visual Studio 에서 보면, 멤버 메서드와 동일하게 보여지고, 사용된다.

다른점은 오른쪽에 (확장) 이라고 표시가 된다.

 

 

 

배열에서의 확장메서드

 

// 정의

public static int[] OrderByAscending(this int[] n)
{
    // to do...
    return outN;
}

 

// 사용시

int[] nList = { 6, 7, 2, 7, 4, 9, 10, 11, 1 };
nList.OrderByAscending();
 

 

 

IEnumerable Generic 에도 이용할 수 있다.

 

// 정의

public static IEnumerable<T> GetOddData<T>(this IEnumerable<T> data)
{
    int n = 1;
    foreach (T Element in data)
    {
 if ((n++ % 2) == 0)
     yield return Element;
    }
}

 

// 사용

List<int> nList = new List<int>() { 1,2,3,4,5,6,7,8,9,10 };
Dictionary<int, string> Dic = new Dictionary<int, string>() { { 1, "one" }, { 2, "two" }, { 3, "three" }, { 4, "four" }, { 5, "five" }, { 6, "six" } };

 

foreach (int val in nList.GetOddData())
{
 Console.WriteLine(val);
}

 

foreach (var kvValue in Dic.GetOddData())
{
 Console.WriteLine(kvValue.Key + " " + kvValue.Value);
}

 

 

이와같이 홀수번째 데이터만 모아서
IEnumerable 로 리턴하는 Extention Method 를 정의하고,


IEnumerable 에서 상속받은 List 와 Dictionary 객체에
GetOddData 메서드를 그대로 사용하는 방법이다.

Posted by 헝개
개발팁2016. 7. 2. 13:28

C# 에서 난수값을 생성하기 위해 사용하는게 Random 클래스인데,

다른 언어에 비해 사용법이 간단하다.

 

Random rnd = new Random();

int val = rnd.Next(1, 100);

 

Random 클래스의 생성자는

 

void Random();

void Random(int seed);

 

2가지가 있는데, 보통 생성자 없이 사용하면, 시간을 seed 로 사용하게 된다.

seed 값을 지정하여,

Random rnd = new Random(DateTime.Now.Millisecond);

이렇게 사용하기도 한다.

seed 값을 고정값을 사용할경우에는, 항상 생성되는 random 값이 동일한 값이 나오게 된다.

 

난수를 생성하는 메서드는 3가지가 있다.

Next() - int 타입의 난수 발생

NextDouble() - double 타입의 난수 발생

NextBytes() - byte 배열의 난수 발생

 

Next 메서드는 3가지 형태로 사용이 가능하다.

1. Next(void) - int 타입의 0 이상의 난수를 발생한다.

2. Next(int maxValue) - 0 이상 maxValue 미만의 난수를 발생한다.

3. Next(int minValue, int maxValue) - minValue 이상 maxValue 미만의 난수를 발생한다. 음수도 가능.

 

여기서 주의할 점은,

난수 범위를 지정할때, 상한범위는 위에저 지정한 maxValue 이라는 값은 절대 나오지 않는다는 것이다.

rnd.Next(1, 100) 이라고 했을때 나올수 있는 난수는 1 ~ 99 까지이다, 절대 100은 나오지 않기 때문에,

사용시에 주의해서 사용해야 한다.

 

위 3가지 int 형 난수 발생시 성능을 한번 테스트 해보았다.

0 ~ 9 까지의 난수를 1000만번 발생시키는 테스트를 해보았다.

1. val = rnd.Next(0, 10);     // 테스트 1

2. val = rnd.Next(10);        // 테스트 2

3. val = rnd.Next() % 10;    // 테스트 3

 

이 3가지는 모두 0 ~ 9 사이의 난수를 발생하는 방법이다.

 

 

// 난수 마지막값은 발생하지 않는것을 확인하기 위해, 배열길이를 11개로 설정
int[] arrVal = new int[11] {0,0,0,0,0,0,0,0,0,0,0};

Random rnd = new Random();
int val;

var stopwatch = new Stopwatch(); // elapsed time 체크를 위해 stopwatch 사용


stopwatch.Start();

 

for (int i = 0; i < 10000000; i++)
{
 val = rnd.Next(0, 10);       // 테스트 1
 //val = rnd.Next(10);        // 테스트 2
 //val = rnd.Next() % 10;    // 테스트 3

 arrVal[val]++;
}

 

stopwatch.Stop();

 

this.txtElapTime.Text = stopwatch.ElapsedMilliseconds.ToString();

this.txtVal0.Text = arrVal[0].ToString();
this.txtVal1.Text = arrVal[1].ToString();
this.txtVal2.Text = arrVal[2].ToString();
this.txtVal3.Text = arrVal[3].ToString();
this.txtVal4.Text = arrVal[4].ToString();
this.txtVal5.Text = arrVal[5].ToString();
this.txtVal6.Text = arrVal[6].ToString();
this.txtVal7.Text = arrVal[7].ToString();
this.txtVal8.Text = arrVal[8].ToString();
this.txtVal9.Text = arrVal[9].ToString();
this.txtVal10.Text = arrVal[10].ToString(); // 마지막값 10은 절대 발생하지 않는다.

 


 

1. rnd.Next(0, 10) 을 사용하여, 1000만개의 난수를 발생하는 시간은 보통 280 ~ 300ms 가 소요.

 

 

2. val = rnd.Next(10) 을 사용하여, 1000만개의 난수를 발생하는 시간은 보통 180 ~ 190ms 가 소요.

 

 

3. rnd.Next() % 10 을 사용하여, 1000만개의 난수를 발생하는 시간은 보통 145 ~ 155ms 가 소요.

 

 

 

이정도면, 큰 차이가 없다고 볼수도 있지만, 미세하게 나마 퍼포먼스 면에서 차이를 보였다.

int Next(void) 가 가장 빨랐고,

int Next(int maxValue) 가 두번째,

int Next(int minValue, int maxValue) 가 가장 느렸다.

 

테스트에 사용된 프로젝트 파일을 다운받아서 확인해보십시요.

 

RandomTest.zip

 

 

 

Posted by 헝개