본 게시글은 지난 내용에서 이어진다.

지난 시간에 앱을 만들기 위한 기초를 구현하였으니, 이번에는 실제 솔리드웍스 API를 사용하여

솔리드웍스 애드온을 만들어보자

 

이번에 해볼 것은 속성과 파일 명을 추출하고 속성 내용을 변경해보는 것을 구현해볼 것이다.

우선 완성 코드를 제공하고 코드 내용을 설명하는 방식으로 설명을 진행하겠다.

 

 

   ~목차

 

1. 준비 -1 프로그램 준비

 

2. 준비 - 2 파일 준비

 

3. 준비 - 3 프로그램 구동 확인

 

4. 코드 구조 살펴보기

 

5. 코드 기능 및 API 살펴보기

 

6. API 자습하기

 

 

 

예시용 솔리드웍스 파일

솔리드웍스2023.zip
2.38MB

 

예시용 프로그램

AddIn.zip
0.11MB


1. 준비 - 1 프로그램 준비

본격적으로 시작하기에 앞서서 먼저 프로젝트를 만들 준비를 하자

더보기

본 프로그램은 MVVM 패턴에 따라 프로그램을 만들 예정이다.

다소 번거롭고 시간을 잡아먹긴 하지만, 추후 유지 보수를 위해 패턴을 도입해서 작성해보자

 

우선 파일 구조다.

 

 

보시다시피 View, ViewModel, Model로 나뉘었으며, 이 중 어느것에도 속하기 애매한 것은 Functions으로 분류할 것이다.

(단, 여기서는 Functions 폴더를 사실상 사용하지 않는다.)

각 파일의 MD는 모델, VM은 뷰모델, EW는 뷰의 약자로 사용했다.

RelayCommand의 경우, 거의 모든 뷰모델에서 사용할 파일이니 명명 규칙에서 예외로 하였으며

그 외 별도의 Model 파일은 없다.

(단, 모델로 분류할 수 있는 부분이 있으나 여기서는 그냥 뷰모델로 분류한다. 이유는 후술)

 

 

NewWindow은 지난번에 만든 솔리드웍스 왼쪽 창에 추가되는 창이고

이번에 새로 만든 것은 EW_MainFunction이다.

 

우선 지난번에 만든 파일을 보자

 

NewWindow.xaml & NewWindow.xaml.cs

NewWindow.xaml 파일 내용
<UserControl x:Class="AddIn.Views.NewWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AddIn.Views"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid Background="Green">
        <TextBlock Text="Example 솔리드웍스에 사용할 예제3" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        <Button Content="버튼!!" Click="Button_Click" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="100" Height="30" Margin="10"/>
    </Grid>
</UserControl>

 

보시다시피 별 내용 없다.

다만 달라진 부분이 있다면 아무기능도 없었던 Button에 이벤트를 부여했다.

이제 버튼을 누르면 새로운 창이 뜨도록 이벤트를 부여했으며 이를 위한 비하인드 코드는 다음과 같다.

using System.Windows;
using System.Windows.Controls;

namespace AddIn.Views
{
    /// <summary>
    /// NewWindow.xaml에 대한 상호 작용 논리
    /// </summary>
    public partial class NewWindow : UserControl
    {
        public NewWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            EW_MainFunction ew_MainFunction = new EW_MainFunction();
            ew_MainFunction.Show();
        }
    }
}

코드를 보면 버튼을 누르면 EW_MainFunction이 뜨도록 했다.

이번엔 이번 내용의 주 무대인 EW_MainFunction 파일을 살펴보자

 

EW_MainFunction.xaml & EW_MainFunction.xaml.cs

<Window x:Class="AddIn.Views.EW_MainFunction"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:AddIn.Views"
            
             xmlns:vm="clr-namespace:AddIn.ViewModels"
        
             mc:Ignorable="d" 
             Width="800" Height="400">
    <Window.DataContext>
        <vm:VM_MainFunction/>
    </Window.DataContext>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>

        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="60"/>
            </Grid.ColumnDefinitions>
            <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                <Label Content="파일 명"/>
                <Label Content="{Binding FileName}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Grid.Column="1">
                <Button Margin="5" x:Name="btnRefresh" Width="50" Command="{Binding RefreshCommand}" Content="새로고침"/>
            </StackPanel>
        </Grid>


        <Grid Grid.Row="1">
            <Grid.RowDefinitions>
                <RowDefinition Height="24"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Label Content="사용자 속성"/>
            <DataGrid x:Name="dgdCustomProperties" Grid.Row="1" Margin="5" AutoGenerateColumns="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding CustomProperties}">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Custom Property Name" Binding="{Binding Name}" IsReadOnly="True"/>
                    <DataGridTextColumn Header="Value" Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>

        <Grid Grid.Row="2">
            <Grid.RowDefinitions>
                <RowDefinition Height="24"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Label Content="설정 속성"/>
            <DataGrid x:Name="dgdConfigurationProperties" Grid.Row="1" Margin="5" AutoGenerateColumns="True" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding ConfigurationProperties}">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Configuration Property Name" Binding="{Binding Name}" IsReadOnly="True"/>
                    <DataGridTextColumn Header="Value" Binding="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
                </DataGrid.Columns>
            </DataGrid>

        </Grid>
    </Grid>
</Window>

 

using System.Windows;

namespace AddIn.Views
{
    public partial class EW_MainFunction : Window
    {
        public EW_MainFunction()
        {
            InitializeComponent();
        }
    }
}

 

xaml의 코드가 제법 길다.

본 코드를 작성할 때 주의할 사항은 코드의 첫부분이 UserControl이 아닌 Window로 시작한다는 것이다.

비주얼 스튜디오에서 xaml 파일을 생성할때 UserControl로 생성하고 헤더를 Window로 바꾸면 된다.

주의! 비하인드 코드에서도 UserControl을 Window로 바꿔주어야 한다.

 

xaml 파일을 만들때는 사용자 정의 컨트롤을 만들고 UserControl 을 Window로 바꿔주자
비하인드 코드에서 상속을 UserControl에서 Window로 바꾸고 xaml에도 바꿔주어야 한다.

 

xaml 파일에서도 UserControl을 Widow로 바꾸자, 이때, 제일 아래에 위치한 </UserControl>도 바꿔주어야 한다.

 

VM_MainFunction.cs

 뷰를 살펴보았으니 이번엔 뷰모델 코드를 살펴보자

using AddIn.Models;
using SolidWorks.Interop.sldworks;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace AddIn.ViewModels
{
    public class VM_MainFunction : INotifyPropertyChanged
    {
        // 데이터 영역
        private string _fileName;
        public string FileName
        {
            get { return _fileName; }
            set { _fileName = value; OnPropertyChanged(); }
        }

        private ObservableCollection<PropertyItem> _customProperties;
        public ObservableCollection<PropertyItem> CustomProperties
        {
            get { return _customProperties; }
            set { _customProperties = value; OnPropertyChanged(); }
        }

        private ObservableCollection<PropertyItem> _configurationProperties;
        public ObservableCollection<PropertyItem> ConfigurationProperties
        {
            get { return _configurationProperties; }
            set { _configurationProperties = value; OnPropertyChanged(); }
        }

        // 커멘드 영역
        public ICommand RefreshCommand { get; }

        // 생성자
        public VM_MainFunction()
        {
            CustomProperties = new ObservableCollection<PropertyItem>();
            ConfigurationProperties = new ObservableCollection<PropertyItem>();
            RefreshCommand = new RelayCommand(GetProperties);
            GetProperties();
        }

        // 속성 가져오기 (사용자, 설정 둘 다)
        private void GetProperties()
        {
            SldWorks swApp = new SldWorks();
            ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;

            if (swModel != null)
            {
                FileName = swModel.GetTitle();
                CustomProperties.Clear();
                ConfigurationProperties.Clear();

                // 커스텀 속성 가져오기
                CustomPropertyManager customPropMgr = swModel.Extension.CustomPropertyManager[""];
                string[] customPropNames = customPropMgr.GetNames();
                foreach (string propName in customPropNames)
                {
                    string valOut;
                    string resolvedValOut;
                    customPropMgr.Get2(propName, out valOut, out resolvedValOut);
                    CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
                }

                // 설정 속성 가져오기
                ConfigurationManager configMgr = swModel.ConfigurationManager;
                Configuration config = configMgr.ActiveConfiguration;
                CustomPropertyManager configPropMgr = config.CustomPropertyManager;
                string[] configPropNames = configPropMgr.GetNames();
                foreach (string propName in configPropNames)
                {
                    string valOut;
                    string resolvedValOut;
                    configPropMgr.Get2(propName, out valOut, out resolvedValOut);
                    ConfigurationProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = false });
                }
            }
            else
            {
                FileName = "열린 파일이 없습니다.";
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class PropertyItem : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; OnPropertyChanged(); }
        }

        private string _value;
        public string Value
        {
            get { return _value; }
            set
            {
                _value = value;
                OnPropertyChanged();
                UpdateProperty();
            }
        }

        public bool IsCustomProperty { get; set; }

        private void UpdateProperty()
        {
            SldWorks swApp = new SldWorks();
            ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;

            if (swModel != null)
            {
                CustomPropertyManager propMgr;
                if (IsCustomProperty)
                {
                    propMgr = swModel.Extension.CustomPropertyManager[""];
                }
                else
                {
                    ConfigurationManager configMgr = swModel.ConfigurationManager;
                    Configuration config = configMgr.ActiveConfiguration;
                    propMgr = config.CustomPropertyManager;
                }

                propMgr.Set2(Name, Value);
            }
        }


        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

 

모든 기능이 이곳에 정의되어 있다.

PropertyItem의 경우, 일부 수정하여 모델에 작성해도 되지만 엄연히 UI와 직접 상호작용하도록 코드를 작성했기에 뷰 모델에 포함시키는 것이 맞다고 판단하여 뷰 모델에 작성했다.

물론 뷰와의 상호작용을 VM_MainFunction 에 다 때려박고 PropertyItem을 모델로 분류해도 된다.

다만 여기서는 뷰모델로 사용하겠다.

이제 Relay_Command를 보자

 

 

 

Relay_Command.cs

using System;
using System.Windows.Input;

namespace AddIn.Models
{
    public class RelayCommand : ICommand
    {
        private readonly Action _execute;
        private readonly Func<bool> _canExecute;

        public RelayCommand(Action execute, Func<bool> canExecute = null)
        {
            _execute = execute ?? throw new ArgumentNullException(nameof(execute));
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute();
        }

        public void Execute(object parameter)
        {
            _execute();
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
    }
}

 

 

MVVM에서 바인딩을 위해 사실상 필수적으로 사용하는 코드

필요에 따라 얼마든지 RelayCommand 내용을 추가할 수 있다, 아래는 추가한 예시

public class RelayCommand<T> : ICommand
{
    private readonly Action<T> _execute;

    public RelayCommand(Action<T> execute)
    {
        _execute = execute;
    }

    public bool CanExecute(object parameter) => true;

    public void Execute(object parameter) => _execute((T)parameter);

    public event EventHandler CanExecuteChanged;
}

이건 예시 코드일 뿐이므로 사용할 필요 없다.

만약 RelayCommand에 파라미터를 넣고자 한다면 이런식으로 추가한다는 예시일 뿐이다.

 


1. 준비 - 2 파일 준비

이번에는 이번 예시에서 사용할 파일을 둘러보자

파일 자체는 첨부해놓았으니 참조 바란다.

 

더보기

 

이번에 사용할 어셈블리 파일

어셈블리라고 하기도 뭐하지만 아무튼 3개의 부품 파일로 구성되어 있다.

이 파일의 속성을 확인해보자

 

 

사진상에는 버튼이 비활성화 되어 있지만 원래 창을 띄우면 저렇게 비활성화 된다.

아무튼 파일 내 사용자 정의와 설정 속성에는 내가 임의로 넣은 값이 들어가 있는 것을 볼 수 있다.

 


3. 준비 - 3 프로그램 구동 확인

마지막으로 프로그램의 구동을 확인해보자

더보기

 

지난 시간에 알려준것 처럼 애드온을 추가하고 버튼을 눌러서 창을 띄워보자

창을 띄우는 순간, 파일명이 들어오고 사용자 속성과 설정 속성들이 추가된 것을 알 수 있다.

이제 이 내용을 편집해보자

 

 

 

내용을 이와 같이 편집했다.

(단, Value가 두곳에 표시되어 있는데 어느쪽을 고쳐도 알아서 상대쪽에 적용된다.)

이제 과연 잘 적용 되었는지 확인해보자

 

 앞서 알려준대로 사용자 정의 값과 설정 속성에 들어가보니 값이 변해 있는 것을 알 수 있다.

 


4. 코드 구조 살펴보기

더보기

이제 코드를 살펴보자

중요한건 뷰모델 파일 안에 다 있기 때문에 해당 내용만 살펴볼 것이다.

이미 한 번 올렸었지만, 가장 중요한 부분인 만큼 다시한번 살펴보자

using AddIn.Models;
using SolidWorks.Interop.sldworks;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;

namespace AddIn.ViewModels
{
    public class VM_MainFunction : INotifyPropertyChanged
    {
        // 데이터 영역
        private string _fileName;
        public string FileName
        {
            get { return _fileName; }
            set { _fileName = value; OnPropertyChanged(); }
        }

        private ObservableCollection<PropertyItem> _customProperties;
        public ObservableCollection<PropertyItem> CustomProperties
        {
            get { return _customProperties; }
            set { _customProperties = value; OnPropertyChanged(); }
        }

        private ObservableCollection<PropertyItem> _configurationProperties;
        public ObservableCollection<PropertyItem> ConfigurationProperties
        {
            get { return _configurationProperties; }
            set { _configurationProperties = value; OnPropertyChanged(); }
        }

        // 커멘드 영역
        public ICommand RefreshCommand { get; }

        // 생성자
        public VM_MainFunction()
        {
            CustomProperties = new ObservableCollection<PropertyItem>();
            ConfigurationProperties = new ObservableCollection<PropertyItem>();
            RefreshCommand = new RelayCommand(GetProperties);
            GetProperties();
        }

        // 속성 가져오기 (사용자, 설정 둘 다)
        private void GetProperties()
        {
            SldWorks swApp = new SldWorks();
            ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;

            if (swModel != null)
            {
                FileName = swModel.GetTitle();
                CustomProperties.Clear();
                ConfigurationProperties.Clear();

                // 커스텀 속성 가져오기
                CustomPropertyManager customPropMgr = swModel.Extension.CustomPropertyManager[""];
                string[] customPropNames = customPropMgr.GetNames();
                foreach (string propName in customPropNames)
                {
                    string valOut;
                    string resolvedValOut;
                    customPropMgr.Get2(propName, out valOut, out resolvedValOut);
                    CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
                }

                // 설정 속성 가져오기
                ConfigurationManager configMgr = swModel.ConfigurationManager;
                Configuration config = configMgr.ActiveConfiguration;
                CustomPropertyManager configPropMgr = config.CustomPropertyManager;
                string[] configPropNames = configPropMgr.GetNames();
                foreach (string propName in configPropNames)
                {
                    string valOut;
                    string resolvedValOut;
                    configPropMgr.Get2(propName, out valOut, out resolvedValOut);
                    ConfigurationProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = false });
                }
            }
            else
            {
                FileName = "열린 파일이 없습니다.";
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public class PropertyItem : INotifyPropertyChanged
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; OnPropertyChanged(); }
        }

        private string _value;
        public string Value
        {
            get { return _value; }
            set
            {
                _value = value;
                OnPropertyChanged();
                UpdateProperty();
            }
        }

        public bool IsCustomProperty { get; set; }

        private void UpdateProperty()
        {
            SldWorks swApp = new SldWorks();
            ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;

            if (swModel != null)
            {
                CustomPropertyManager propMgr;
                if (IsCustomProperty)
                {
                    propMgr = swModel.Extension.CustomPropertyManager[""];
                }
                else
                {
                    ConfigurationManager configMgr = swModel.ConfigurationManager;
                    Configuration config = configMgr.ActiveConfiguration;
                    propMgr = config.CustomPropertyManager;
                }

                propMgr.Set2(Name, Value);
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

 

 해당 코드에서 주서로 내용을 나누어놨는데, 그 내용을 살펴보자

 

데이터 영역

        // 데이터 영역
        private string _fileName;
        public string FileName
        {
            get { return _fileName; }
            set { _fileName = value; OnPropertyChanged(); }
        }

        private ObservableCollection<PropertyItem> _customProperties;
        public ObservableCollection<PropertyItem> CustomProperties
        {
            get { return _customProperties; }
            set { _customProperties = value; OnPropertyChanged(); }
        }

        private ObservableCollection<PropertyItem> _configurationProperties;
        public ObservableCollection<PropertyItem> ConfigurationProperties
        {
            get { return _configurationProperties; }
            set { _configurationProperties = value; OnPropertyChanged(); }
        }

 이 부분은 사용할 데이터에 관한 부분이다.

예를 들어 FileName은 프로그램 상에 파일 이름이 표시될 공간에 사용할 데이터다.

이미지로 보여주자면 다음과 같다.

 

그런데 왜 FileName 변수가 _fileName까지 합쳐서 총 2번 선언되어 있는지 궁금할 수 있는데

_fileName은 클래스 내부에서만 접근이 가능하며 데이터가 실제로 저장되는 공간의 역할을 하며

FileName은 _fileName을 UI에 노출시키고 만약 어떠한 사유로든 값이 변경되었을때, 그 사실을 알리기 위한 역할이다.

즉 _fileName은 데이터를 보관하기 위한 곳이고 FileName은 데이터가 변경되었을때 그 사실을 알리기 위한 속성이다.

따라서, 프로그램 내부 (백엔드)에서 사용할 값과 외부(프론트엔드) 가 사용할 값 2개를 쓴다고 생각하면 된다.

 

자, 이제 CustomProperties와 ConfigurationProperties를 살펴보자

 

이것들의 값은 각각 사용자 속성 값과 설정 속성 값이다.

그런데 fileName과는 코드 선언 형태가 다른데

이는 fileName은 하나의 데이터만 저장되는 반면

CustomProperties와 ConfigurationProperties는 여러개의 데이터를 저장하기 때문이다.

 

당장 프로그램만 봐도 fileName에는 오직 현재 열린 파일의 이름 하나만 이름이 나타나있고

사용자 속성과 설정 속성에는 데이터가 여러개가 들어와 있는 것을 알 수 있다.

여기서 Property는 사용자 정의와 설정 속성으로 2개가 있기 때문에 2개를 만들었다.

 

커멘드 영역

 

커멘드 영역은 뷰모델에서 뷰에 이벤트를 전달하는 용도로 사용한다.

        public ICommand RefreshCommand { get; }
        // 생성자
        public VM_MainFunction()
        {
            RefreshCommand = new RelayCommand(GetProperties);
            // 나머지 생략
        }

 

생성된 커멘드를 생성자에서 RelayCommand를 통해 전달해준다.

이때 전달되는 것은 GetProperties 함수다.

 

사실 생성자 내부를 보면 GetProperties()가 한번 더 있는것을 알 수 있는데

이는 창이 나타났을때 이벤트를 한 번 더 실행하라는 의미이다.

즉, GetProperties 이벤트는 창이 처음 열렸을 때 한번, 그리고 새로고침 버튼을 누를 때 마다 실행된다.

 

PropertyItem 클래스

전달될 데이터에 대한 부분으로 매우 중요한 부분 중 하나다.

코드를 하나하나 살펴보자

 

우선 코드의 윗부분을 살펴보자

이 부분은 Name과 Value로 구성되어 있다.

앞서 설명한 바와 같이 _name은 프로그램 내부에 저장되는 값이며

때문에 Name은 _name의 값을 가져온다.

새로 값이 할당될 때는 할당된 값을 _name에 할당하되 OnPropertyChanged 이벤트 (값이 변경되었음을 알리는 함수)도 같이 작동시킨다.

 

Value의 경우도 비슷해보이나 OnPropertyChanged  함수 뿐만 아니라 UpdateProperty 함수도 같이 사용하는 것을 알 수 있다.

UpdateProperty 함수는 무엇일까?

 

 

이건 솔리드웍스에 값을 업데이트 할 것을 지시하는 부분이다.

그러나 이 단락에서는 코드의 구조를 살펴보는 것이 주 목적이었기 때문에

이 부분에 대해서는 다음 단락에 대해 다루도록 하겠다.

 

 


5. 코드 기능 살펴보기

더보기

자, 가장 중요한 부분이다. API가 어떻게 작동하고 또 솔리드웍스 내의 데이터에 어떻게 접근하는지 알아보자

우선 GetProperties 함수다.

 

 

       // 속성 가져오기 (사용자, 설정 둘 다)
       private void GetProperties()
       {
           SldWorks swApp = new SldWorks();
           ModelDoc2 swModel = (ModelDoc2)swApp.ActiveDoc;

           if (swModel != null)
           {
               // 파일 이름 가져오기
               FileName = swModel.GetTitle();
               CustomProperties.Clear();
               ConfigurationProperties.Clear();

               // 커스텀 속성 가져오기
               CustomPropertyManager customPropMgr = swModel.Extension.CustomPropertyManager[""];
               string[] customPropNames = customPropMgr.GetNames();
               foreach (string propName in customPropNames)
               {
                   string valOut;
                   string resolvedValOut;
                   customPropMgr.Get2(propName, out valOut, out resolvedValOut);
                   CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
               }

               // 설정 속성 가져오기
               ConfigurationManager configMgr = swModel.ConfigurationManager;
               Configuration config = configMgr.ActiveConfiguration;
               CustomPropertyManager configPropMgr = config.CustomPropertyManager;
               string[] configPropNames = configPropMgr.GetNames();
               foreach (string propName in configPropNames)
               {
                   string valOut;
                   string resolvedValOut;
                   configPropMgr.Get2(propName, out valOut, out resolvedValOut);
                   ConfigurationProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = false });
               }
           }
           else
           {
               FileName = "열린 파일이 없습니다.";
           }
       }

 

윗부분 부터 하나하나 살펴보자

 

 

SldWorks는 솔리드웍스 프로그램 자체를 의미한다.

SldWorks는 API 사용의 첫걸음이며, API에 접근하기 위해 가장 먼저 작성하게 되는 부분이다.

 

이 코드에서는 우선 솔리드웍스 프로그램 인터페이스를 새로 생성하고 있다.

이를 통해, 현재 열려있는 솔리드웍스와 직접 연결할 수 있다.

 

솔리드웍스에서 다루는 파일은 크게 부품, 어셈블리, 도면 이렇게 3개로 나뉘는데

이 3가지중 어떤것이라도 할당하기 위해서 ModelDoc2 키워드를 사용한다.

만약 어셈블리는 명시적으로 할당하고 싶다면 AssemblyDoc 키워드를, 부품을 명시적으로 할당하고 싶다면 PartDoc 키워드를, 도면을 명시적으로 할당하고 싶다면 DrawingDoc 키워드를 사용하면 된다.

 

자, 이제 ModelDoc2에 무엇을 할당할지 정해야 하는데 코드에서는 swApp.ActiveDoc 라고 되어있다.

이는 현재 솔리드웍스(swApp) 에서 활성화된 문서 (ActiveDoc)를 사용한다는 뜻이다.

앞에 (ModelDoc2) 가 있는데 이는 호환을 위해 데이터 타입을 변환해주기 위해 붙여둔 것이다.

(사실 없어도 큰 문제는 없지만 코드의 안정성을 위해 넣어준거다.)

 

 

여담

ModelDoc2  키워드에 2가 붙었느냐고 생각할 수 있는데, 과거 ModelDoc는 과거 API 기능이 부실했던 때에 만들어진 것으로 여러가지 기능을 추가하면서 ModelDoc2 키워드를 사용하게 되었다. (단 ModelDoc 는 지금도 쓸 수는 있다.)

앞서 우리는 swModel에 "지금 열려져 있는 파일"을 할당바 있다.

그런데 만약 열려져 있는 파일이 없다면 swModel은 null이 될 것이고

이후의 모든 코드가 에러가 될 것이다.

이를 막기 위해 본 코드에서 swModel != null 조건을 추가하였다.

 

자 첫번째 기능이다. 

열려져 있는 파일의 이름을 가져오자

FileName은 앞서 정의했던 그 변수들이다.

혹시나 해서 그 변수 코드를 다시 사진으로 넣으니 참조하기 바란다.

 

 

FileName = swModel.GetTitle();

swModel은 앞서 말했듯이 "지금 열려져있는 파일"을 뜻하고 GetTitle()메서드는 그 파일의 이름을 가져오는 기능을 수행한다.

 

자, 그런데 CustomProperties와 ConfigurationProperties에 대한 부분도 있다.

이 부분은 Clear()함수가 적용되어 있는데 이는 해당 내용을 모두 없앤다는 뜻이다.

여기서는 변수 내용을 모두 정리하고 새로 할당할 것이 때문에 없애는 코드를 넣어놨다.

 

파일이름을 가져오는 것은 너무 쉬웠으니 이번에는 속성 값들을 가져와보자

 

먼저 커스텀 속성이다.

 

ConfigurationManager 가 바로 커스텀 속성을 가져오기 위한 키워드가

역시나 이곳에서도 swModel을 사용해서 값을 할당하고 있다.

Extension은 확장 기능을 위한 부분이다.

즉 커스텀 속성은 확장기능에 속하기 때문에 해당 메서드를 통해서만 접근할 수 있다.

참고로 확장 기능 중에는 다른 이름으로 저장 (SaveAs)도 있다.(심지어 이 키워드는 SaveAs3까지 있다.)

왜 굳이 이렇게 해놨는지는 웍스 개발자들만이 알것이다.

 

또한 [""]는 커스텀 속성의 기본 구성을 가져오기 위해 사용한다.

(사실 왜 굳이 [""]를 사용해야 하도록 해놨는지는 잘 모르겠다.)

 

 

아무튼 "열려있는 파일"의 "사용자 속성"을 가져와서 customPropMgr에 할당하였으니 이것을 데이터 그리드에 표시해보자

 

 

string[] customPropNames = customPropMgr.GetNames();

사용자 정의 속성의 이름들을 가져와 문자열 배열로 할당한 다음

      foreach (string propName in customPropNames)
      {
            string valOut;
            string resolvedValOut;
            customPropMgr.Get2(propName, out valOut, out resolvedValOut);
            CustomProperties.Add(new PropertyItem { Name = propName, Value = resolvedValOut, IsCustomProperty = true });
        }

그 이름들을 순회하면서 customPropMgr의 Get2메서드를 사용하여 값을 가져온다.

 

값을 가져온다음, 앞서 Clear 했던 CustomProperties에 새로운 요소로 추가한다.

이때 값과 벨류, IsCustomProperty  값이 추가된다.

그런데 CustomProperties는 앞서 View파일의 데이터 그리드에 바인딩 된 값이다.

즉, 여기에 값이 추가되면서 자동으로 데이터 그리드에 값이 올라간다.

 

 

설정 속성도 크게 다르지 않지만 사용자 정의 속성이랑은 조금 차이점이 보이는데 이는 다음과 같다.

 

ConfigurationManager 이것은 솔리드웍스 모델의 모든 구성을 관리하는 키워드이고

Configuration은  솔리드웍스 모델의 특정 구성을 나타내기 위한 클래스다.

그리고 위의 사용자 속성 처럼 이름을 가져와서 찾는 방식으로 값을 가져오고 ConfigurationProperties에 할당한다.

 

 

일단 여기까지가 데이터를 가져오고 할당하는 부분이다.

이제 다음 UpdateProperty()로 가보자

 

 여기에도 어김없이 SldWorks 키워드와 ModelDoc2 키워드가 사용되고 있다.

 IsCustomProperty는 변경된 값이 사용자 정의 값인지, 설정 속성 값인지를 구분하고 있고

실제 값을 할당하는 부분은 가장 아래 Set2 함수에 해당한다.

값을 가져올때 Get2 함수를 사용했으니 할당할 때는 Set2 함수를 사용하는 것이다.

 

 

 


6. API 자습하기

더보기

이번 글을 작성하면서 솔직히 후반부로 갈 수록 알아듣기 어렵고 설명이 부족하다는 것을 느꼈을 탠데

인벤터 API와 솔리드웍스 API를 사용해보면서

개인적으로는 솔리드웍스 API가 다소 복잡하고 난잡하게 잡혀있으며 설명도 부실한 부분이 많아

답정너 식으로 이해하는 방법 밖에 없었다.

 

(뿐만 아니라 같거나 거의 비슷한 기능임에도 불구하고 버전만 다른 키워드도 많다. 예를 들어 ModelDoc만 해도 ModelDoc, ModelDoc2, IModelDoc, IModelDoc2 가 있다.)

 

그래도 이번에 본 게시글에서 언급한 부분 말고도 알고자 하는 부분이 있을 것이고

보다 다양한 기능을 API를 통해 사용하고 싶은 사람들을 위해 API 정보를 얻는 방법에 대해 간단히 서술하고 글을 마치겠다.

 

Welcome - 2023 - SOLIDWORKS API Help

 

 

이는 API 도움말 문서이다.

영어밖에 없기 때문에 브라우저에서 필요에 따라 번역을 하도록 하고

우선 우측 체크박스의 버전을 선택하자 

 

 

버전을 확인했으면 다음은 키워드다.

예를 들어, 지금까지 사용했던 ModelDoc2 키워드를 검색해보자

 

 

 

 검색했으면 좌측의 API Help를 눌러서 API 관련된 내용만 보자

 

 

그러면 해당 키워드의 인터페이스 내용이 나올탠데 이것을 보면 된다.

 

그런데 ModelDoc2를 검색했는데 IModelDoc2 가 나온 이유는 ModelDoc2 키워드의 기능을 IModelDoc2 키워드가 대체했기 때문이라고 한다.

그러니까 ModelDoc2 키워드는 사용은 할 수 있어도 지원은 끝났고 앞으로는 IModelDoc2 키워드를 쓰라는 뜻이다.

 

뭐 이렇게 힘들게 해놨을까

 

 

아무튼 검색해보면 이렇게 여러가지 내용이 있다.

본보기라고 번역된 부분은 예시를 말한다.

사용 방법과 예시등이 나와 있으니 해당 문서를 보고 공부하면 된다.

 

 

(그런데 사실 그마저도 불친절하기 그지없다. 그냥 아는 사람한테 물어보는게 더 빠를 수도 있다.)

 

 

 

 

 

본 게시글은 비주얼스튜디오 2022와 .NetFrameWork를 통해서 솔리드웍스 앱을 만드는 기본적인 과정을 서술한다.

 

 

1. 프로젝트 생성-1 참조 추가

 

2. 프로젝트 생성-2 프로젝트 작성

 

3. 레지스트리 등록

 

4. 강력한 키 (서명) 생성

 

5. 등록

 

6. 확인

 

부록

 

순서대로 진행한다.

또한 여기에 내가 만든 프로젝트 파일도 업로드 해놓았으나

내가 만든것을 다운로드 하기 보단 이 게시글을 하나하나 쫒아가는 것을 권장한다.

 

 

 

AddIn.zip
0.07MB

 


1. 프로젝트 생성-1 참조추가

더보기 참조

더보기

 

비주얼 스튜디어에서 새 프로젝트를 만들자

 

 

검색창에서 클래스를 입력하고 나온 메뉴들 가운데 클래스 라이브러리 (.NET FrameWork)를 선택한다.

 

주의: 반드시 .NetFramework를 선택한다. 그냥 클래스 라이브러리로 할 경우 프로젝트 속성 목록이 다르게 나타난다.

 

 

 

적당히 원하는 곳에 프로젝트 생성

 

 

 

기본적으로 Class1 파일이 생성되어 있다.

그건 그냥 놔두고

오른쪽 메뉴에서 참조를 우클하고 참조 추가 선택

 

 

 

참조 관리자의 어셈블리 탭에서 위의 2개를 추가

 

 

이번엔 참조관리자의 찾아보기 탭에서 찾아보기 버튼 선택

 

 

 

위의 경로에서 파일 3개를 찾아서 추가

 

완료

 


2. 프로젝트 생성-2

더보기

 

 

 

생성된 xaml에 본인이 원하는 배치를 하도록 하자

비하인드 코드는 건드릴 필요 없다.

적당히 추가한 코드 내용은 아래와 같다.

    <Grid Background="Green">
        <TextBlock Text="Example 솔리드웍스에 사용할 예제3" FontSize="20" VerticalAlignment="Center" HorizontalAlignment="Center"/>
        <Button Content="버튼!!" VerticalAlignment="Bottom" HorizontalAlignment="Center" Width="100" Height="30"/>
    </Grid>

 

 

 

 

다시 Class1.cs 파일로 돌아와서 코드를 입력한다.

 

 public class MyAddIn : SwAddin
 {

     private ISldWorks _swApp;
     private int _addinID;
     private ITaskpaneView taskPaneView;


     public bool ConnectToSW(object ThisSW, int Cookie)
     {
         _swApp = (ISldWorks)ThisSW;
         _addinID = Cookie;

         // Task Pane 추가
         taskPaneView = (ITaskpaneView)_swApp.CreateTaskpaneView2("", "WPF로 만든 또다른 창!");

         // WPF 유저 컨트롤을 ElementHost에 할당
         var elementHost = new ElementHost
         {
             Child = new NewWindow(), // WPF 유저 컨트롤 설정
             Dock = DockStyle.Fill // 크기를 Task Pane에 맞게 설정
         };

         // DisplayWindowFromHandle 메서드에 IntPtr로 바로 전달
         taskPaneView.DisplayWindowFromHandle(elementHost.Handle.ToInt32());

         return true;
     }


     public bool DisconnectFromSW()
     {
         if (taskPaneView != null)
         {
             taskPaneView.DeleteView();
         }
         return true;
     }

 }

 

 

그런데 이거대로 입력할 경우 사진 처럼 오타 판정이 있을 수 있다.

 

오타 판정이 나타난 곳을 클릭한 다음 Alt + Enter를 입력하자

 

 

위 사진대로 Alt+Enter를 통해 첫번째 메뉴를 클릭하면 코드의 최상단에 using이 자동으로 생성되는 것을 확인할 수 있다.

 

 

이제 GUID를 만들어보자

 

GUID 코드는 자신이 무작위로 생성해도 무방하지만

각 컴퓨터 안에는 이미 매우 다양한 GUID가 있고 이것과 중복되서는 안된다.

따라서 GUID 생성 툴을 사용해보자

 

 

 

 

 

아래 "결과"에 GUID가 생성되었으면 우측 상단의 복사를 클릭

 

 

 

그 다음 class MyAddIn위에 붙여넣기 하고

그 아래 

[ComVisible(true)를 입력한다.]

 

 

여기까지 마쳤으면 빌드 진행

 


3. 레지스트리 등록하기

더보기

 

 

 

 

 

 

 

 

 

 

 


4. 강력한 키 (서명) 생성

더보기

 

 

 

 

 

 

 

MyAddinKey는 생성할 서명 파일의 이름으로 사용자가 원하는 이름 지정

단, 확장자 .snk는 반드시 붙여야함

 

 

 

 이제 프롬프트에서 벗어나 비주얼스튜디오로 복귀

 

 

 

 

 

 

 

 

 


5. 등록

더보기

 

아까처럼 프롬프트를 실행

 

 

 

이번엔 cd를 할 필요 없다.

 

이번 프롬프트에서 실행할 명령어는 아래와 같으나 주의사항이 몇가지 있음

 

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe "C:\Users\손민성\Desktop\업무_개발\2_진행\5_퀵메니저\프로그램\테스트프로그램\TestProgram3\AddIn\bin\Debug\AddIn.dll" /tlb /codebase


이 명령어는
 -   RegAsm 프로그램의 위치
 -   내가 만든 프로그램의 위치
 -   /tlb

 -   /codebase

이 4개로 구성되어 있다.

 

앞부분이 RegAsm 프로그램의 위치

그 다음 초록색 부분이 내가 만든 dll 파일의 위치, 나머지는 옵션이다.


따라서 RegAsm 프로그램이 저 위치에 있는지 확인해야 한다.

(사용자마다 RegAsm 프로그램의 위치가 조금씩 다를 수 있기 때문)

 

 

 

 

 이렇게 RegAsm.exe 파일의 위치를 찾을 수 있다.

 

이제 내 dll 파일의 위치를 찾아보자

 

 

 

 

 

 

이제 RegAsm.exe 파일의 위치는

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe

 

내가 만든 dll 파일의 위치는 

"C:\Users\손민성\Desktop\업무_개발\2_진행\5_퀵메니저\프로그램\테스트프로그램\TestProgram3\AddIn\bin\Debug\AddIn.dll"

 

이렇게 찾아냈다.

 

이제

/tlb 과

 

/codebase 를 붙여서 (띄어쓰기로 나누어주는 것 잊지말것)

아까 그 명령어를 완성하자

 

C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe "C:\Users\손민성\Desktop\업무_개발\2_진행\5_퀵메니저\프로그램\테스트프로그램\TestProgram3\AddIn\bin\Debug\AddIn.dll" /tlb /codebase

 

 

 

 

 

 


6. 확인

이제 모든 준비가 완성되었다.

솔리드웍스를 실행해서 제대로 추가되었는지 확인해보자

더보기

 

솔리드웍스를 실행하고 상단의 톱니바퀴 -> 애드인 -> 가장 아래로 스크롤

 

아까 레지스트리 등록할때 있었던 파일이 있다.

이것의 왼쪽의 체크박스만 체크하고 확인

 

 

 

 이렇게 아까 만든 WPF 창이 나타나는 것을 볼 수 있다.

그런데 뭔가 좀 이상하게 나타났는데

 

 

 창의 크기를 좌우로 조절해주면 알아서 맞춰진다.

 


부록

 

이 글을 작성하면서 참고했던 영상이다.

아래 영상은 10년전의 것이며 영어로 작성되어 있기 때문에 최신 버전과는 약간의 차이가 있다.

 

Create and register SolidWoks C# and VB.NET AddIns in 3 Steps (youtube.com)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

프로그램을 실행하다보면 여러가지 이유로 에러가 발생할 수 있다.

이럴 경우, 보통 프로그램은 멈추거나 꺼지거나 하게되는데 이런 일을 막기위한 것이 바로 예외처리다.

 

예외처리에는 여러가지 방법이 있는데 다음과 같다.

 

try-catch

try - catch문은 대강 다음과 같이 사용한다.

 

try{
	//원하는 내용 입력
}
catch (FormatException e){
	//원하는 내용 입력
}

try의 {} 내에는 시험할 내용을 입력한다.

catch는 try의 {} 안에서 에러가 발생할 경우, catch로 넘어오게 한다.

 

예를 들어서

try{
	1번코드
	2번코드
	3번코드
	4번코드
}
catch (FormatException e){
	5번코드
}

이 코드는 1번코드 -> 2번코드 -> 3번코드... 순서대로 실행된다.

그런데 이 코드에서 만약 2번 코드를 실행하던 중 에러가 발생할 경우 바로 catch문인 5번코드로 넘어가게 된다.

 

만약 어떤 코드에서도 에러가 발생하지 않았다면? 4번코드까지 실행하고 5번코드는 실행되지 않는다.

 

여기서 catch의 FormatException e 는 발생한 에러를 말한다.

예를 들어, 에러 발생시, 에러 메시지를 출력한다면

try{
}
catch (Exception e){
	Console.WriteLine(e.Message);
}

이런 식으로 입력하면 된다.

 

Exception

에러는 많은 종류를 가지고 있으며 그 종류와 용도는 다음과 같다.

ArgumentException
메서드에 전달 되는 null이 아닌 인수가 잘못되었다.

ArgumentNullException
메서드에 전달 되는 인수가 null이다.
ArgumentOutOfRangeException
인수가 유효한 값 범위를 벗어났다.
DirectoryNotFoundException
디렉터리 경로 일부가 잘못되었다.
DivideByZeroException
0으로 나누었다.
DriveNotFoundException
드라이브가 없거나 사용할 수 없다.
Exception
모든 종류의 예외를 처리할 수 있다.
FileNotFoundException
파일이 없다.
FormatException
문자열에서 변환할 수 있는 적절 한 형식이 아니다.
IndexOutOfRangeException
인덱스가 배열 또는 컬렉션의 범위를 벗어났다.
InvalidOperationException
개체의 현재 상태에서 메서드 호출을 사용할 수 없다.
KeyNotFoundException
컬렉션의 멤버에 액세스 하는 데 지정 된 키를 찾을 수 없다.
NotImplementedException
메서드 또는 작업이 구현 되지 않았다.
NotSupportedException
메서드 또는 작업이 지원 되지 않는다.
ObjectDisposedException
삭제 된 개체에 대한 작업을 수행했다.
OverflowException
산술, 캐스팅 또는 변환 작업을 수행 하면 오버플로가 발생한다.
OverflowException
작업 결과가 대상 데이터 형식의 범위를 벗어났다.
PathTooLongException
경로 또는 파일 이름이 시스템에서 정의한 최대 길이를 초과한다.
PlatformNotSupportedException
현재 플랫폼에서 작업이 지원 되지 않는다.
RankException
차원 수가 잘못되었다.
TimeoutException
작업에 할당 된 시간 간격이 만료 되었다.
UriFormatException
잘못 된 URI (Uniform Resource Identifier)가 사용 되었다.

 

여기서 가장 많이, 대중적으로 사용하는 키워드가 바로 Exception 이다.

 

또한 이 exception의 하위 키워드들은 모두 exception의 상속을 받은 서브 클래스다.

때문에 아래처럼 사용은 불가능하다.

try {
}
catch (Exception e)
{
}
catch (OverflowException e)
{
}

이미 가장 큰 개념인 Excepton에서 모든 에러를 검출했기 때문에 두번째 catch문은 에러가 난다.

때문에 아래와 같이 사용해야 한다.

try {
}
catch (OverflowException e)
{
}
catch (Exception e)
{
}

작은 부분을 먼저 검출하고, 그 다음 나머지를 검출한다.

 

사실 Exception 만 알아도 왠만한건 다 커버가 된다.

 

try-catch-finally

finally는 try구문을 실행하던 중 에러가 발생하든 하지 않든 실행하는 부분이다.

즉 에러가 발생하지 않아도 실행하고 에러가 발생해도 실행하는 부분이다.

try를 쓸 때는 catch가 필수이지만 finally는 필수가 아니다.

static void ThrowFunc(int data) {
    if(data > 0) {
        Console.WriteLine("ThrowFunc data: " + data);
    }
    else {
        throw new Exception("data에 0이 입력되었습니다.");
    }
}

static void Main(string[] args) {
    try {
        ThrowFunc(10);
        ThrowFunc(100);
    }
    catch(Exception e) {
        Console.WriteLine(e.Message);
    }
    finally {
        Console.WriteLine("  finally 무조건 실행  \n");
    }
}

이 코드를 실행할 경우, try에서 에러가 발생하든 하지 않든 상관없이 finally는 무조건 실행된다.

에러를 일으키고 싶다면 ThrowFunc에서 파라미터 값을 10이 아닌 0이나 그 이하값을 주면 된다.

 

throw

try-catch가 실행 중 에러 검출이라면 throw는 원하는 타이밍에 에러를 낸다고 볼 수 있다.

 

if(data > 0) {
    Console.WriteLine("정상실행");
}
else {
    throw new Exception("에러발생");
}

위 구문에서, data가 0보다 크면 정상실행되지만 아닐 경우, throw를 통해서 에러를 반환한다.

참고로 이때 키워드 또한 Exception 말고도 위에서 표로 작성했던 다른 하위 Exception을 사용할 수도 있다.

 

직접만드는 예외 클래스

앞서 말했듯이 exception은 많은 하위 클래스를 가지고 있다.

마찬가지로, 임의로 클래스를 가져와 상속을 해주는 형태로 새로운 예외클래스를 만들 수 있다.

 

class MyException : ApplicationException
{
    public int Num { get; set; }
    public MyException():base() {
    }
    public MyException(int a) {
        Num = a;
    }

    public override string ToString() {
        return "Num: " + Num;
    }
}

 

ApplicationException을 상속받아 나만의 애러를 만들었다.
ApplicationException의 base를 상속 받고 정수를 받는데, MyException이 호출되면  Num값이 에러메시지로 출력된다.

 

 

 

일반화

일반화의 특징과 장점

클래스, 함수 일반화 가능

<T> 키워드 사용

박싱과 언박싱을 줄일 수 있음

불필요한 오버로딩을 줄일 수 있다

 

<T>키워드는 굳이 T일 필요는 없다. (U로 하든 AAA로 하든 자유다.)

 

아래 예시를 보자

static void GenericPrint<T>(T data) {
    Console.WriteLine("data: {0}", data);
}

static void GenericPrint<T>(T[] arrData) {
    for(int i = 0; i < arrData.Length; i++) {
        Console.WriteLine("arrData: {0}", arrData[i]);
    }
}

함수는 GenericPrint고 파라미터 하나를 받는다.

그리고 Console을 통해, 입력받은 파라미터를 출력한다.

 

두번째 함수는 같은 이름이지만 배열을 받는다.

 

static void Main(string[] args) {
    int a = 10;
    float b = 10.3f;
    string c = "Hello";

    int[] arrA = { 0, 1, 2, 3 };
    string[] arrB = { "A", "B", "C", "D" };

    GenericPrint(a);
    GenericPrint(b);
    GenericPrint(c);

    Console.WriteLine();
    GenericPrint(arrA);
    Console.WriteLine();
    GenericPrint(arrB);
}

메인함수의 모습, 첫번째 함수에 정수를 넣든, 소수를 넣든 문자열을 넣든 잘 작동한다.

 

이를 통해 알 수 있는 것은

일반화를 하면 데이터에 종속되지 않고 나름 확장성을 지니게 되는 것이다.

 

일반화 - 클래스

클래스에서는 일반화 키워드를 클래스 이름과 함께 적는다.

class GenericAA<T>
{
    private T num;
    public T GetNum() {
        return num;
    }

    public void SetNum(T data) {
        num = data;
    }
}

이걸 메인함수로 가져와서 사용하면 아래와 같다.

class Program
{
    static void Main(string[] args) {
        GenericAA<int> AA = new GenericAA<int>();
        AA.SetNum(100);
        Console.WriteLine("AA: " + AA.GetNum());

        GenericAA<float> BB = new GenericAA<float>();
        BB.SetNum(100.30f);
        Console.WriteLine("BB: " + BB.GetNum());
    }
}

메인함수에서는 new를 통해 새로 생성할 때, 키워드에 자료형을 넣어준다.

이로써 클래스를 실행할 때, 넣어줄 값을 정해줄 수 있다.

 

일반화를 통해서, 클래스는 다양한 종류의 값을 받을 수 있게 된다.

 

 

dynamic

dynamic은 var타입이 가지고 있는 문제를 해결하는 용도로 사용할 수 있다.

또한 var와 object와 비슷한 느낌이 있다.

 

static T AddArray<T>(T[] arrDatas) {
    //T temp = 0; 에러 발생
    //object aaa = 0; //박싱, 언박싱 발생
    dynamic temp = default(T); 
    for(int i = 0; i < arrDatas.Length; i++) {
        temp += arrDatas[i];
    }
    return temp;
}

 

위 코드에서, 첫 번째 주석에서, T는 0으로 지정할 수 없다.

왜냐하면 T를 입력받을 때, 그 자료형이 무엇인지 알 수 없기 때문에 정수형으로 넣어줄 수 없는 것이다.

하지만 dynamic의 경우, var나 object처럼 다양하게 값을 받을 수 있으므로 에러가 발생하지 않는다.

 

dynamic과 var와 object는 어떻게 다를까?

var와 dynamic은 선언된 원본 형식을 유지한다.

그러나 이 둘은 타입이 확정되는 시점이 다르다.

var는 컴파일 시점에 이미 타입을 추정하여 확정하지만

dynamic은 런타임 시점에 타입이 확정된다.

때문에 dynamic은 올바르지 않은 타입이 지정되더라도 컴파일 시점에서는 모두 pass되며 런타임 시점에 에러가 발생한다.

 

일반화 컬렉션

 

C# 코드 최상단에 다음을 추가하여 사용할 수 있다.

using System.Collections.Generic;

 

이것은 컬렉션의 박싱과 언박싱의 단점을 해결하며 4가지 방식이 있다.

List<T>
Queue<T>
Stack<T>
Dictionary<T>

이것은 자료를 보관하는 통과 같으나

어떻게 보관하는지, 또 꺼낼때는 어떻게 꺼내는지에 따라 차이가 있다.

 

List부터 시작해보자

List

list는 말 그대로 데이터 리스트다.

후술할 다른 것들과는 다르게 딱히 데이터를 꺼낸다던가 하는 개념은 없고 인덱스를 통해 접근할 수 있다.

내부요소를 지울때는 clear()를 사용한다.

static void Main(string[] args) {
    List<int> ListAA = new List<int>();
    ListAA.Add(1);
    ListAA.Add(2);

    for(int i = 0; i < 10; i++) {
        ListAA.Add(i);
    }

    foreach(var data in ListAA) {
        Console.WriteLine("data: " + data);
    }

    Console.WriteLine("\n배열데이터로 초기화");
    string[] arrData = { "AA", "BB", "CC" };
    List<string> listArr = new List<string>(arrData);

    for(int i = 0; i < arrData.Length; i++) {
        Console.WriteLine("arrData: " + arrData[i]);
    }
}

ListAA는  정수형만 받을 수 있게 되어있다.

그렇게 정수를 받고 foreach로 값을 출력한다.

이후 문자열만 받는 새로운 List를 만들고 거기에arrData를 넣은 다음 그걸 다시 출력한다.

 

출력결과

 

Queue

static void Main(string[] args) {
    Queue<int> queueAA = new Queue<int>();
    queueAA.Enqueue(1);
    queueAA.Enqueue(2);

    for(int i = 0; i < 10; i++) {
        queueAA.Enqueue(i);
    }

    Console.WriteLine("queue data: {0}", queueAA.Peek());

    while(queueAA.Count > 0) {
        Console.WriteLine("queue data: {0}, count: {1}", queueAA.Dequeue(), queueAA.Count);
    }

    Console.WriteLine("\n배열데이터로 초기화");
    string[] arrData = { "AA", "BB", "CC"};
    Queue<string> queueArr = new Queue<string>(arrData);

    foreach(var data in queueArr) {
        Console.WriteLine("queueArr data: " + data);
    }
}

큐는 파이프와 같다.

값을 꺼낼 때, 가장 오래된 데이터가 나온다.

지금 for을 통해서, 1에서 9까지 차례대로 데이터를 넣고 있는데

Dequeue를 통해서 꺼낸 값은 가장 먼저 들어간 값부터 차례대로 나오고 있다.

출력결과

 

 

Stack

 

static void Main(string[] args) {
    Stack<string> stack = new Stack<string>();
    stack.Push("a");
    stack.Push("b");
    stack.Push("c");

    Console.WriteLine("stack data: {0}", stack.Peek());

    while(stack.Count > 0) {
        Console.WriteLine("stack data: {0}, count: {1}", stack.Pop(), stack.Count);
    }

    //배열데이터로 초기화
    Console.WriteLine("\n배열데이터로 초기화");
    int[] arrData = { 100, 200, 300 };
    Stack<int> stackCopy = new Stack<int>(arrData);

    foreach(object data in stackCopy) {
        Console.WriteLine("stackCopy data: " + data);
    }
}

stack에선 Push로 값을 넣고 Peek로 그 값을 살펴보고

Count로 값을 세고 Pop으로 값을 꺼낸다.

 

stack은 통 안에 쌓는 느낌이다.

이는 주로 실행취소(undo) 같은 기능에 사용하는데

Pop을 통해 데이터를 꺼내면 가장 최근에 입력된 데이터가 출력된다.

Peek는 지금 당장 꺼낼 수 있는 값이 무엇인지, 즉, stack의 최상단에 위치한 값을 출력(확인)한다.

 

출력결과

 

 

 

 

 

Dictionary

딕셔너리는 자바스크립트나 웹개발에서 자주 사용되는 그것과 비슷하다.

여러가지 데이터를 제목과 내용 같은 느낌으로 저장하는데 사실 제한이 없다.

여기서 제목에 속하는것이 key, 내용에 해당하는 것이 value다.

예를들어, AA 딕셔너리의 1번키는 a_a 1번벨류는 a_b, 2번키는 b_a, 2번 벨류는 b_b... 이런 형태로 저장되는 것이다.

즉 AA의 1번 키의 벨류값을 가져오라고 한다면 a_b가 나올 것이다.

 

static void Main(string[] args) {
    Dictionary<string, int> dictionaryAA = new Dictionary<string, int>();
    dictionaryAA.Add("10", 10);
    dictionaryAA.Add("20", 20);
    dictionaryAA["30"] = 30;

    foreach(var key in dictionaryAA.Keys) {
        Console.WriteLine("key: {0}, data: {1}", key, dictionaryAA[key]);
    }

    Console.WriteLine("");

    Dictionary<int, string> dictionaryInit = new Dictionary<int, string>() { 
        [1] = "Hello",
        [100] = "Dictionary",
        [1000] = "Good!!",
    };

    foreach(var key in dictionaryInit.Keys) {
        Console.WriteLine("key: {0}, data: {1}", key, dictionaryInit[key]);
    }

    string getValue = string.Empty;
    dictionaryInit.TryGetValue(1, out getValue);
    Console.WriteLine("\nTryGetValue: " + getValue);

    dictionaryInit.TryGetValue(11, out getValue);
    Console.WriteLine("\nTryGetValue: " + getValue);

 

딕셔너리를 처음 선언하는 곳을 보면 string과 int로 선언되었는데, 사실 2개 말고도 더 많이 넣을 수도 있다.

 

foreach를 보면 dictionaryAA.keys 라고 되어 있는데, 이는 dictionaryAA가 가진 key값들을 말한다.

 

출력결과

key가 key값이고 data가 그 key가 가진 value값이다

 

 

 

 

 

 

 

 

 

 

 

 

 

다형성 (virtual, override)

다형성은 객체지향의 핵심으로 함수의 오버라이딩(재정의)하는 중요한 부분이다.

 

지난번과 비슷하게 Super라는 부모 클래스를 만들고 AA와 BB라는 자식 클래스를 만들었다

 

Main 함수다.

자, 이 코드를 실행하면 어떤 결과가 나올까?

굉장히 특이한 결과가 나온것을 알 수 있다.

 

대체 어떻게 된것인지 차근차근 알아보자

 

결과 사진에서 num: 0은 Main 함수의 super.Print()이다.

 

결과 사진 2~3번 줄은 aa.Print();의 것으로 AA 클래스가 아닌 Super클래스에서 가져온 것임을 알 수 있다.

 

 

다른 예시

 

일단 이론은 이런데 뭔가 굉장히 어렵고 복잡하다.

다시 알아듣기 쉽게 설명해보자

 

예를 들어보자

스타크래프트 다들 알 것이다.

스타크래프트에는 테란 종족이 있다.

 

자, 테란에 속해있는 유닛과 건물들이다 (숫자는 무시하자)

테란에게는 프로토스와 달리 보호막이 없고 저그처럼 재생되지 않는다는 특징이 있다.

그리고 이 건물과 유닛들은 이 '테란'이라는 특징을 상속받는다.

 

즉 테란이 base 클래스인 것이다.

건물은 생략하고 마린과 파이어뱃만 놓고 보자

이들에게는 체력, 공격력, 이동속도, 생산비용 등등의 데이터가 있을 것이다.

이걸 코드화를 하면 다음과 같다.

 

먼저 부모클래스인 테란이다.

자식클래스 1인 마린

자식클래스 2인 파이어뱃이다.

 

부모클래스에서 Run 함수는 자식클래스에서 재정의된다.

공격함수에서 테란의 공격함수를 가져오지만, 마린은 총격을, 파뱃은 화염공격을 실행한다.

 

이렇게 특정 무언가를 부모클래스에서 가져오고, 그것을 유닛들 특성에 맞게 뜯어고쳐주는 것이 바로 다형성이다.

 

자, 그렇다면

이걸 Main함수에서는 어떻게 사용할까?

 

Main함수에서 Marine 을 3, FireBat을 1개 선언했다.

즉 Terran이 총 4개가 선언이 되었고 그것들이 각각 마린과 파이어뱃으로 재선언되었다.

이렇게 4개나 되는 것들이 하나의 포문으로 동시에 Run함수나 Attack함수를 사용한다.

 

이게 다형성의 장점이며 보다 깊게 파고들면 게임 개발등에도 요긴하게 사용할 수 있을 것을 보인다.

 

 

실행 결과

 

 

클래스에서 가장 중요한 부분중 하나인 상속에 대해서 알아보자

 

상속

클래스에는 부모자식 관계라는 것이 있다.

부모클래스를 base, parent, 상위, super 등으로 부르고

자식클래스를 derived, child, 파생, sub 등으로 부른다.

 

예를들어, 옷 이라는 개념을 보자

옷에는 봄옷, 여름옷, 가을옷, 겨울옷 이라는 하위 개념이 있고

그 중 겨울옷에는 패딩, 코트, 목도리와 같은 하위 개념이 있으며

패딩에는 롱패딩, 숏패딩이라는 하위개념이 있다.

 

여기서 옷이 부모클래스라면 겨울옷은 자식클래스이며

겨울옷이 부모클래스라면 패딩이나 코트가 자식클래스가 되는 것이다.

 

즉, 상위 개념과 하위 개념이라고 생각할 수 있다.

 

그렇다면 상속은 어디에 사용할까?

 

게임에는 몬스터가 있고 몬스터 종류로 스켈레톤과 슬라임이 있다고 가정해보자

같은 몬스터인 스켈레톤과 슬라임은 분명 공유하는 무언가가 있을 것인데

이 공유하는 무언가를 몬스터라는 상위 개념에서 상속받아서 사용한다.

 

아래 예시를 보자

class Super
{
    protected int a;			// 새로운 접근 지정자
    public void Print() {
        Console.WriteLine("Super Print()");
    }
}

class Sub:Super
{
    int b;
    public void Print() {
        Console.WriteLine( a, b);
    }
}

시작하기 전에 Super 클래스의 protected int a; 가 있는데

여기서 protected라는 접근 지정자는 본인 클래스와 본인 클래스의 상속을 받은 클래스만 접근할 수 있다는 뜻이다.

참고로 int a의 접근지정자가 public이어도 sub클래스에서 사용할 수 있지만 private 일 경우는 불가능 하다.

 

Super 라는 클래스가 부모 클래스이고 아래 Sub라는 클래스가 자식 클래스다.

Sub는 Super의 상속을 받는다.

때문에 Sub클래스의 Print를 작동하면 Super의 a를 가져와서 사용한다.

 

상속 - 생성자와 소멸자

class Super
{
    public Super() {
        Console.WriteLine("Super 생성");
    }
    ~Super() {
        Console.WriteLine("Super 소멸");
    }
}
class Sub:Super
{
    public Sub() {
        Console.WriteLine("Sub 생성");
    }
    ~Sub() {
        Console.WriteLine("Sub 소멸");
    }
}

class Program
{
    static void Main(string[] args) {
        Sub sub = new Sub();
    }
}

위 코드를 요약하면 부모와 자식 클래스에서 생성자와 소멸자를 호출하고

Main 함수에서는 자식클래스만 불러오는 것이다.

 

출력 결과는 부모 생성자 -> 자식 생성자 -> 자식 소멸자 -> 부모 소멸자 순서대로 진행된다.

그런데 만약 Main 함수에서 Sub sub = new Sub();로 자식 클래스를 사용하는 것이 아닌

Super super = new Super();로 부모클래스를 부른다면 출력결과가

 

부모 생성자 -> 부모 소멸자 로 자식 클래스는 완전히 빠지게 된다.

 

base

base키워드는 부모의 생성자 함수를 불러오는데 사용한다.

this와도 유사한 부분이 보이는데 이는 후술한다.

위 코드에서, 자식 클래스인 Sub에서 Sub 생성자가 두 개의 파라미터를 받는데

그 중 num에 해당하는 것을 부모 클래스의 Super의 파라미터(int num)에 대입하겠다는 것을 뜻한다.

 

또, Sub 클래스에서 PrintSub() 함수를 보면

base.name 이 있는데 이는 부모 클래스에 있는 name을 뜻한다.

 

어찌보면 this 하고 비슷한데, this 가 클래스 본인 자체를 말하는 거라면

base는 자신을 상속해준 부모클래스를 뜻하는 키워드라고 볼 수 있다.

 

is

is는 객체의 형식을 검사한다.

bool을 리턴한다는 특징이 있다.

먼저 Base 클래스를 만들고 Base 클래스를 상속받는 AA 클래스와 BB 클래스를 만들었다.

 

Main 함수에선 AA 클래스를 가져오는데 Base에 넣었기 때문에 aa는 Base 클래스를 가져온 것이다.

이 때문에, 바로 아랫 줄에선 aa.PrintA() 가 아닌 aa.Print()가 사용된 것이다.

(이부분 헷갈릴 수 있으므로 주의)

 

자, Base에 AA를 담에서 aa를 선언했다.

그렇다면 aa는 AA가 되어야 할 것이니 바로 아래 조건문에서는 

aa is AA가 true가 된다.

 

 

as

as는 일종의 강제 형 변환이라고 볼 수 있다.

null을 리턴한다는 특징이 있다.

위의 코드에서 Main 함수를 바꾼 것이다.

bb는 본래 Base에 담긴 BB였고 때문에 Base 클래스의 Print 함수에 접근할 수 있었다.

그러나 Base에서 BB로 형변환이 일어나면서 BB 클래스의 PrintB 함수에 접근할 수 있게 되었다.


사실 이 기능은 숙이 BB copyBB = bb as BB 가 아닌

BB copyBB = (BB)bb;를 사용할 수도 있다.

그러나 그러지 않는 이유는 예외발생으로 인한 애러를 방지하기 위해서 일부러 as를 사용한다.

 

사실 정확히 어떤 문제가 발생하는지는 잘 모르겠으나 잘못된 형변환을 할 경우 as 없이 사용하면

에러가 발생할 수 있다고 한다.

 

다형성

 

 

 

 

 

 

 

 

 

this

객체 자신을 참조하기 위한 키워드다.

class AA{
	int a;
	public AA (int a){
		this.a = a;
	}
}

위 예시에서, this.a = a 에서 this.a는 어디의 a를 나타내는 것이고

오른쪽의 a는 어디의 a를 나타내는 것일까?

 

this 키워드가 붙은 쪽은 클래스에 선언된 a를 뜻하고 그렇지 않은 쪽은 생성자의 파라미터를 말한다.

 

 

static

지금까지 줄창나게 나왔지만 정작 설명은 안해줬던 그 키워드다.

static이란 클래스의 멤버를 객체 생성없이 사용 가능하게 해준다.

여기서 멤버란 필드와 메소드로 구성된 것을 말한다.

class AA
{ 
    public static int a;
    public static int b;
    public int c;

    //정적 함수는 반드시 정적 변수만 참조 가능
    public static void Print() {
        Console.WriteLine("a: {0}", a);
        Console.WriteLine("b: {0}", b);
        //Console.WriteLine("c: {0}", c); //불가능
    }
}

class BB
{
    public int a;
    public int b;

    public void Print() {
        Console.WriteLine("a: {0}", a);
        Console.WriteLine("b: {0}", b);
    }
}

위 코드에서 클래스 AA와 BB가 있는데

AA는 static이 있고 BB는 없다.

여기서, AA 클래스의 c에는 static이 없는데, 이 때문에 아래 Print함수에서 사용하지 못하는 것을 볼 수 있다.

즉, static 함수는 static 변수만 참조할 수 있다.

 

이제 이 두 클래스를 써보자

class Program
{
    static void Main(string[] args) {
        AA.a = 10;  
        AA.b = 100;
        AA.Print();

        //BB.a //new를 통해 객체생성을 하지 않았기에 사용불가
        //BB.b
        //BB.Print();

        BB bb = new BB();
        bb.a = 100;
        bb.b = 200;

        bb.Print();
    }
}

static으로 만들어진 클래스는 new를 사용한 객체생성 없이 사용할 수 있다.

이 때문에 AA.a를 바로 접근할 수 있는 반면

BB.a는 new를 사용해 객체생성을 하기 전까지는 사용할 수 없다.

 

클래스 활용

클래스는 파라미터나 리턴으로 사용할 수 있다.

 

class AA
{
    public int a;
    public int b;
    public AA() {
        a = 0;
        b = 0;
    }
    public void Print() {
        Console.WriteLine(a);
        Console.WriteLine(b);
    }
}

class Program
{
    static void Main(string[] args) {
        AA aa = new AA();
        aa.Print();

        CopyRefClass(aa);
        aa.Print();

        AA aaa = CopyDeepClass(aa);
        aaa.Print();
        aa.Print();
    }

    static void CopyRefClass(AA aa) {
        AA refAA = aa;
        refAA.a = 100;
        refAA.b = 10000;
    }


    static AA CopyDeepClass(AA aa) {
        AA tempAA = new AA();

        tempAA.a = aa.a;
        tempAA.b = aa.b;

        tempAA.a = 0;

        return tempAA;
    }
}

 

코드가 뭔가 길다.

그러나 천천히 살펴보자

AA 클래스는 a값과 b값을 출력하는 역할을 한다.

메인 함수에서는 AA 클래스를 객체생서을 하고 Print 함수를 실행해 a값과 b값을 출력한다.

 

이후 CopyRefClass함수로 aa를 복제하는데 CopyRefClass는 여기서 값을 바꾸면

원본 값도 바뀌도록 주소를 가져와 값을 부여하고 있다.

 

이번엔 CopyDeepClass함수로 aa를 복제하는데 생성자를 통해 복제를 하는 것을 볼 수 있다.

생성자로 복제한 이후 복제된 a값과 b값에 각각 aa.a의 값과 aa.b의 값을 넣어주고 있는데

이 값들은 이미 바로 위에서 100과 10000으로 바뀌었기 때문에 여기서도

a엔 100, b에는 10000이 들어간다.

그러나 마지막에 a값을 0으로 해주었으니 결과적으로 0, 10000이 된다.

 

자, 마지막으로 Main함수에서 다시 Print 함수를 실행한다.

그러자 이번엔 100, 10000이 찍히는 것을 알 수 있다.

 

 

위 코드를 실행하면 그 결과는 다음과 같다.

맨 처음에는 a와 b가 둘 다 0이었으니 0과 0이 찍힌다.

그러나 다음에서 a값과 b값의 원본을 100과 10000으로 바꾸어주었다,

그 다음에는 AA의 사본을 만들어서 편집을 했고

때문에 원본 값은 바뀌지 않고 사본값만 노출된다.(0, 10000)

 

마지막으로 원본 값을 다시 확인해보니

바뀌지 않은 것을 알 수 있다.

 

 

 

 

 

 

 

 

 

 

 

클래스

클래스란 사용자가 직접 만드는 일종의 틀이라고 할 수 있다.

변수(필드)와 함수(메소드)를 하나의 단위로 결합해주는 역할을 한다.

 

상속, 다형성, 파생 클래스, 클래스의 특수화 메커니즘 등이 있다.

접근 한정자

public class AAAA
{
	public int x, y;

	public AAAA()
	{
		실행문 내용 생략
	}

}

위 코드에서, public이 접근 한정자에 해당하고

접근 한정자는 public, protected, internal, protected internal, private, private protected가 있다.

public은 밖에서 액세스가 가능한 것을 의미한다.

protected는 이 클래스와 이 클래스에서 파생된 클래스로만 액세스 할 수 있다.

internal은 현재 어셈블리로만 액세스가 제한된다.

protected internal는 포함하는 클래스와 포함하는 클래스에서 파생된 클래스와 어셈블리내의 클래스만 액세스가 된다.

private는 이 클래스에서만 접근이 가능하다

private protected는 포함하는 클래스와 동일 어셈블리 내의 포함하는 유형으로부터 파생된 클래스만 액세스가 된다.

 

...뭔가 엄청 복잡하고 많은데 사실 대부분은 잘 안쓰는 거고

public과 private를 가장 많이 사용하며 protected도 종종 사용된다.

 

아래 예시를 보자

class AA{
	int a;
	public int b, c;

	public void Print(){
		Console.WriteLine(a,b,c);
	}
}

class Program
{
	static void Main(string[] args){
		AA aa = new AA();
		//aa.a = 5; 는 사용불가
		aa.b = 10;
		aa.c = 20;
		aa.Print();
	}
}

AA 클래스에는 int가 각각 a, b, c가 선언되어 있다.

이때, a만 접근한정자가 없는데, 이 경우 private가 기본적으로 적용된다.

b와 c는 public으로 정의되어 있으며

Print함수 또한 public으로 정의되어 있다.

 

아래 Program 클래스를 보자

Main함수에서, 후술할 생성자를 통해 AA 클래스를 쓰겠다고 했으며 이를 aa라고 명명했다.

AA 클래스에는 변수 a, b, c와 Print 함수가 있는데

b와 c의 값은 여기서 정해주는 것이 가능하나 a는 정해줄 수 없다.

 

왜냐하면 private 인 값은 다른 클래스에서 조작할 수 없기 때문이다.

반면 public인 b와 c와 Print는 얼마든지 사용가능한 것을 볼 수 있다.

 

 

생성자와 소멸자

바로 위에서, Main함수에서 AA 클래스를 사용하기 위해 new 키워드를 쓴 것을 볼 수 있다.

이것이 바로 생성자로 객체를 생성할 때 사용한다.

 

즉, AA클래스를 새로 하나 복사를 해서 이 클래스에서 쓰겠다는 것이며 이 사본의 이름을 aa라고 지은 것이다.

 

그렇다면 소멸자는 뭘까

소멸자는 GC라고도 하며 ~키워드를 사용한다.

여기서 ~는 클래스 앞에 붙여서 사용한다.

만약 위의 AA클래스에 소멸자를 붙인다면 ~AA가 되는 것이다.

 

아래 예시를 보자

class AA{
	int a;
	float b;

	public AA(){
		a = 0;
		b = 0f;
	}

	public AA(int aa, float bb){
		a = aa;
		b = bb;
	}

	~AA(){
		Console.WriteLine("제거")
	}

}

class Program
{
	static void Main(string[] args){
		AA aa = new AA();
		AA aaaa = new AA(100, 0.5f);
	}
}

시작하기 전에, AA클래스 안에는 AA가 총 두 종류가 있는 것을 볼 수 있다.

하나는 파라미터를 2개 받는 것이고 나머지 하나는 파라미터를 안 받는 것이다.

 

Main 함수에서, 파라미터를 받는 함수와 받지 않는 함수를 각각 따로 호출하고 있다.

여기서 소멸자는 AA 클래스 내의 모든 호출이 끝나면 자동으로 소멸자가 호출한 횟수만큼 작동한다.

위 코드에서는 AA 클래스를 총 2번 호출했으므로 "제거"라는 문자열이 두 번 등장할 것이다.

 

 

주의사항

생성자와 소멸자의 이름은 클래스의 이름과 같다.

생성자와 소멸자는 리턴이 없다.

생성자는 접근한정자를 쓰고 클래스의 이름을 넣어주면 된다.(자료형을 넣지 않는다.)

소멸자에는 어떠한 파라미터도 넣어줄 수 없다.

클래스 안에 생성자가 하나라도 선언되어 있을 경우 기본으로 제공하는 선언자가 작동하지 않는다.

이게 무슨말이냐면 아래 코드를 보자

class AA{
	public AA(){
	}
	public AA(int aa, float bb){
	}
}

class Program
{
	static void Main(string[] args){
		AA aa = new AA();
		AA aaaa = new AA(100, 0.5f);
	}
}

이 코드에는 문제가 없다.

그러나 AA 클래스의 public AA를 없애버리면

Main 함수의 AA aa = new AA(); 가 동작하지 않는다.

때문에 클래스에 생성자를 넣어줄 때는

반드시 빈 생성자라도 추가를 해주어야 한다.

 

 

 

 

+ Recent posts