Страница: 1 | 2 |
Вопрос: Сжатие данных
Добавлено: 05.01.07 14:41
Автор вопроса: Raider
Никто не пробывал писать архиватор? А то возникла в нем потребность, но не знаю с чего начать. Может подскажите?..
Ответы
Всего ответов: 16
Номер ответа: 1
Автор ответа:
Alexandr.R
Вопросов: 9
Ответов: 115
Web-сайт:
Профиль | | #1
Добавлено: 05.01.07 15:28
Namespace Compression
#Region " NAMESPACE : Huffman "
Namespace Huffman
#Region " CLASS : Compressor "
Public Class Compressor
Private Overloads Function Equals(ByVal objA As Object, ByVal objB As Object) As Boolean
End Function
Private Overloads Function ReferenceEquals(ByVal objA As Object, ByVal objB As Object) As Boolean
End Function
Public ReadOnly Property Value() As Long
Get
Dim lngValue As Long
If Not (Me.fsInput Is Nothing) Then
Try
lngValue = Me.fsInput.Position
Catch ex As Exception
End Try
End If
Return lngValue
End Get
End Property
Public ReadOnly Property Length() As Long
Get
Static lngLength As Long = -1
If lngLength = -1 Then
Dim fileInfo As New IO.FileInfo(Me.Input)
lngLength = fileInfo.Length
End If
Return lngLength
End Get
End Property
Private Input As String
Private Output As String
Private fsInput As IO.FileStream
Private fsOutput As IO.FileStream
Private bsInput As IO.BufferedStream
Private bsOutput As IO.BufferedStream
Private TreeNodes() As Internal.HuffmanTreeNode
Public Sub New(ByVal Input As String, ByVal Output As String)
Me.Input = Input
Me.Output = Output
End Sub
Public Sub Compress()
'get the tree
Me.TreeNodes = Huffman.Internal.GetHuffmanTreeNodes(Me.Input)
Huffman.Internal.GetHuffmanTree(Me.TreeNodes)
'open input and output files.
Me.fsInput = New IO.FileStream(Me.Input, IO.FileMode.Open)
Me.fsOutput = New IO.FileStream(Me.Output, IO.FileMode.Create)
Me.bsInput = New IO.BufferedStream(Me.fsInput, Huffman.Internal.BufferSize)
Me.bsOutput = New IO.BufferedStream(Me.fsOutput, Huffman.Internal.BufferSize)
'write the XML
Dim strXML As String = Huffman.Internal.NodesToString(Me.TreeNodes)
Dim bytXML() As Byte = System.Text.Encoding.Default.GetBytes(strXML)
'write the length of the xml, in a ten byte long.
Dim xmlLength As String = bytXML.Length.ToString
Do Until xmlLength.Length = 10 : xmlLength = " " & xmlLength : Loop
Dim bytXmlLength() As Byte = System.Text.Encoding.Default.GetBytes(xmlLength)
Me.bsOutput.Write(bytXmlLength, 0, bytXmlLength.Length)
'increment 10 bytes so the leftover bits can be written here.
Me.bsOutput.Position += 10
'write the xml bytes.
Me.bsOutput.Write(bytXML, 0, bytXML.Length)
'wrote the xml.
'write the compressed data.
Dim binaryDecimalConverter As New Internal.BinaryDecimalConverter
Dim strBinary As String
Dim readByte As Integer
Do Until readByte = -1
readByte = Me.bsInput.ReadByte
If Not readByte = -1 Then
Dim strPath As String = Me.TreeNodes(readByte).Path
strBinary = strBinary & strPath
End If
If strBinary.Length > 8 Then
Dim byteOut As Byte = CByte(binaryDecimalConverter.GetDecimal(strBinary.Substring(0, 8)))
Me.bsOutput.WriteByte(byteOut)
strBinary = strBinary.Remove(0, 8)
End If
Loop
'finished writing compressed data.
'write the leftover bits, starting at byte 10 of the file.
If Not strBinary = "" Then
Do Until strBinary.Length = 10 : strBinary = " " & strBinary : Loop
Dim bytLeftovers() As Byte = System.Text.Encoding.Default.GetBytes(strBinary)
Me.bsOutput.Position = 10
Me.bsOutput.Write(bytLeftovers, 0, bytLeftovers.Length)
End If
'finished writing leftover bits.
Me.bsInput.Close()
Me.bsOutput.Close()
Me.fsInput.Close()
Me.fsOutput.Close()
End Sub
End Class
#End Region
#Region " CLASS : Decompressor "
Public Class Decompressor
Private Overloads Function Equals(ByVal objA As Object, ByVal objB As Object) As Boolean
End Function
Private Overloads Function ReferenceEquals(ByVal objA As Object, ByVal objB As Object) As Boolean
End Function
Public ReadOnly Property Value() As Long
Get
Dim lngValue As Long
If Not (Me.fsInput Is Nothing) Then
Try
lngValue = Me.fsInput.Position
Catch ex As Exception
End Try
End If
Return lngValue
End Get
End Property
Public ReadOnly Property Length() As Long
Get
Static lngLength As Long = -1
If lngLength = -1 Then
Dim fileInfo As New IO.FileInfo(Me.Input)
lngLength = fileInfo.Length
End If
Return lngLength
End Get
End Property
Private Input As String
Private Output As String
Private fsInput As IO.FileStream
Private fsOutput As IO.FileStream
Private bsInput As IO.BufferedStream
Private bsOutput As IO.BufferedStream
Private Tree As Internal.HuffmanTreeNode
Private currentNode As Internal.HuffmanTreeNode
Public Sub New(ByVal Input As String, ByVal Output As String)
Me.Input = Input
Me.Output = Output
End Sub
Public Sub Decompress()
Me.fsInput = New IO.FileStream(Me.Input, IO.FileMode.Open)
Me.fsOutput = New IO.FileStream(Me.Output, IO.FileMode.Create)
Me.bsInput = New IO.BufferedStream(Me.fsInput, Huffman.Internal.BufferSize)
Me.bsOutput = New IO.BufferedStream(Me.fsOutput, Huffman.Internal.BufferSize)
'get the xml length.
Dim bytTreeNodesAsStringLength(9) As Byte
Me.bsInput.Read(bytTreeNodesAsStringLength, 0, bytTreeNodesAsStringLength.Length)
Dim treeNodesAsStringLength As Integer = Integer.Parse(System.Text.Encoding.Default.GetString(bytTreeNodesAsStringLength).Replace(" ", "")
'get the leftover bits.
Dim bytLeftOvers(9) As Byte
Me.bsInput.Read(bytLeftOvers, 0, bytLeftOvers.Length)
Dim strLeftovers As String = System.Text.Encoding.Default.GetString(bytLeftOvers).Replace(" ", ""
'get the tree
Dim bytXml(treeNodesAsStringLength - 1) As Byte
Me.bsInput.Read(bytXml, 0, treeNodesAsStringLength)
Dim strXML As String = System.Text.Encoding.Default.GetString(bytXml)
Dim TreeNodes() As Internal.HuffmanTreeNode = Huffman.Internal.StringToNodes(strXML)
Me.Tree = Huffman.Internal.GetHuffmanTree(TreeNodes)
Me.currentNode = Me.Tree
'got the tree
'decompress the file.
Dim binaryDecimalConverter As New Internal.BinaryDecimalConverter
Dim readByte As Integer
Do Until (readByte = -1)
readByte = Me.bsInput.ReadByte
If Not readByte = -1 Then
Dim strBinary As String = binaryDecimalConverter.GetBinary(readByte)
Me.DecryptBits(strBinary)
End If
Loop
'decrypt the leftovers too.
Me.DecryptBits(strLeftovers)
Me.bsInput.Close()
Me.bsOutput.Close()
Me.fsInput.Close()
Me.fsOutput.Close()
End Sub
Private Sub DecryptBits(ByVal Bits As String)
Dim x As Integer
For x = 0 To (Bits.Length - 1)
Select Case Bits.Chars(x).ToString
Case "0"
Me.currentNode = Me.currentNode.Left
Case "1"
Me.currentNode = Me.currentNode.Right
End Select
If Not (Me.currentNode.Character = -1) Then
Me.bsOutput.WriteByte(CByte(Me.currentNode.Character)) 'write a byte.
Me.currentNode = Me.Tree 'go back to the beginning of the tree.
End If
Next x
End Sub
End Class
#End Region
#Region " CLASS : Internal "
Friend Class Internal
Public Const BufferSize As Integer = (1024) * 4
#Region " FUNCTION : GetHuffmanTreeNodes "
Public Shared Function GetHuffmanTreeNodes(ByVal File As String) As HuffmanTreeNode()
Dim HuffmanTreeNodes(255) As HuffmanTreeNode
Dim x As Integer
For x = 0 To 255
HuffmanTreeNodes(x) = New HuffmanTreeNode
HuffmanTreeNodes(x).Character = x
Next x
Dim fileStream As New IO.FileStream(File, IO.FileMode.Open)
Dim readByte As Integer
Do Until readByte = -1
readByte = fileStream.ReadByte
If Not readByte = -1 Then
HuffmanTreeNodes(readByte).Weight += 1
End If
Loop
fileStream.Close()
Return HuffmanTreeNodes
End Function
#End Region
#Region " FUNCTION : GetHuffmanTree "
Public Shared Function GetHuffmanTree(ByVal TreeNodes() As HuffmanTreeNode) As HuffmanTreeNode
'get the last two elements.
'combine them into a node. (which is not a leaf)
'kill them from nodelist.
'add(New node, weight = 1 + 2)
'sort the list again and join the last 2 elements (do this until only 1 element exists)
'this makes it so that changes to each treenode are reflected in TreeNodes,
'but changes to the array order stay the same.
Dim tempTreeNodes() As HuffmanTreeNode = CType(TreeNodes.Clone, HuffmanTreeNode())
Do Until tempTreeNodes.Length = 1
Array.Sort(tempTreeNodes, New HuffmanTreeNodeComparer)
Dim node1 As HuffmanTreeNode = tempTreeNodes(tempTreeNodes.Length - 1)
Dim node2 As HuffmanTreeNode = tempTreeNodes(tempTreeNodes.Length - 2)
Dim newNode As New HuffmanTreeNode
newNode.Character = -1
newNode.Weight = node1.Weight + node2.Weight
newNode.Left = node1
newNode.Right = node2
node1.Parent = newNode
node2.Parent = newNode
ReDim Preserve tempTreeNodes(tempTreeNodes.Length - 3)
ReDim Preserve tempTreeNodes(tempTreeNodes.Length)
tempTreeNodes(tempTreeNodes.Length - 1) = newNode
Loop
Return tempTreeNodes(0)
End Function
#End Region
#Region " FUNCTION : NodesToString/StringToNodes "
Public Shared Function NodesToString(ByVal TreeNodes() As HuffmanTreeNode) As String
Dim characters(255) As String
Dim weights(255) As String
Dim x As Integer
For x = 0 To 255
characters(x) = CStr(TreeNodes(x).Character)
weights(x) = CStr(TreeNodes(x).Weight)
Next x
Return String.Join("#", characters) & "%" & String.Join("#", weights)
End Function
Public Shared Function StringToNodes(ByVal Xml As String) As HuffmanTreeNode()
Dim characters() As String = Xml.Split("%".ToCharArray)(0).Split("#".ToCharArray)
Dim weights() As String = Xml.Split("%".ToCharArray)(1).Split("#".ToCharArray)
Dim TreeNodes(255) As HuffmanTreeNode
Dim x As Integer
For x = 0 To 255
TreeNodes(x) = New HuffmanTreeNode
TreeNodes(x).Character = CInt(characters(x))
TreeNodes(x).Weight = CLng(weights(x))
Next x
Return TreeNodes
End Function
#End Region
#Region " CLASS : HuffmanTreeNode "
Public Class HuffmanTreeNode
Public Sub New()
End Sub
Private Shadows Function Equals() As Boolean
Return False
End Function
Private Shadows Function ReferenceEquals() As Boolean
Return False
End Function
Public Character As Integer = -1 'the character found in the file.
Public Weight As Long = 0 'amount of times the character was found in the file.
<System.Xml.Serialization.XmlIgnore()> Public Parent As HuffmanTreeNode 'the parent node.
<System.Xml.Serialization.XmlIgnore()> Public Left As HuffmanTreeNode 'the left leaf.
<System.Xml.Serialization.XmlIgnore()> Public Right As HuffmanTreeNode 'the right leaf.
<System.Xml.Serialization.XmlIgnore()> Public ReadOnly Property Path() As String 'the binary path to the node.
Get
Static strPath As String
If strPath Is Nothing Then
If Not (Me.Parent Is Nothing) Then
If (Me.Parent.Left Is Me) Then strPath = "0"
If (Me.Parent.Right Is Me) Then strPath = "1"
strPath = Parent.Path & strPath
End If
End If
Return strPath
End Get
End Property
End Class
#End Region
#Region " CLASS : HuffmanTreeNodeComparer "
Public Class HuffmanTreeNodeComparer
Implements IComparer
Public Sub New()
End Sub
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
Dim returnVal As Integer = -1
Dim a As Double = CType(x, HuffmanTreeNode).Weight
Dim b As Double = CType(y, HuffmanTreeNode).Weight
If a > b Then
returnVal = 1
ElseIf a < b Then
returnVal = -1
ElseIf a = b Then
returnVal = 0
End If
returnVal *= -1 'descending sort order... (biggest to smallest)
Return returnVal
End Function
End Class
#End Region
#Region " CLASS : BinaryDecimalConverter "
Public Class BinaryDecimalConverter
Private hashGetDecimal As Hashtable
Private hashGetBinary As Hashtable
Public Function GetDecimal(ByVal strBinary As String) As Integer
If Me.hashGetDecimal Is Nothing Then
Me.hashGetDecimal = New Hashtable
Dim x As Integer
For x = 0 To 255
Me.hashGetDecimal(Me.GetBinaryInternal(x)) = x
Next x
End If
Return CInt(Me.hashGetDecimal(strBinary))
End Function
Public Function GetBinary(ByVal intDecimal As Integer) As String
If Me.hashGetBinary Is Nothing Then
Me.hashGetBinary = New Hashtable
Dim x As Integer
For x = 0 To 255
Me.hashGetBinary(x) = Me.GetBinaryInternal(x)
Next x
End If
Return CStr(Me.hashGetBinary(intDecimal))
End Function
Private Function GetBinaryInternal(ByVal intDecimal As Integer) As String
Dim Y As Integer
Dim strBinary As String
While (intDecimal \ 2) > 0
Y = intDecimal \ 2
If intDecimal > 1 Then
strBinary = Val(CStr(intDecimal - (Y * 2))) & strBinary
End If
intDecimal = Y
End While
strBinary = intDecimal & strBinary
Do Until strBinary.Length = 8
strBinary = "0" & strBinary
Loop
Return strBinary
End Function
Private Function GetDecimalInternal(ByVal strBinary As String) As Integer
Dim sngNumber As Integer
Dim x As Integer
Dim Tmp As Integer
Dim Output As Integer
sngNumber = CInt(strBinary)
For x = 0 To Len(CStr(sngNumber)) - 1
Tmp = CInt(Right(CStr(sngNumber), 1))
If Tmp = 1 Then
Tmp = CInt(Tmp * Math.Pow(2, x))
End If
Output = Output + Tmp
Tmp = 1
If Len(CStr(sngNumber)) > 1 Then
sngNumber = CInt(Left(CStr(sngNumber), Len(CStr(sngNumber)) - 1))
Else
sngNumber = 0
End If
Next
Return Output
End Function
Private Function GetPowerInternal(ByRef Value As Integer, ByRef Power As Integer) As Integer
Dim result As Integer
If Power > 1 Then
Dim x As Integer
For x = 2 To Power
Value = Value * 2
Next
result = Value
Else
If Power = 0 Then result = 1
If Power = 1 Then result = Value
End If
Return result
End Function
End Class
#End Region
End Class
#End Region
End Namespace
#End Region 'works ok. not the best compression ratio though.
#Region " NAMESPACE : AdaptiveHuffman "
'Namespace AdaptiveHuffman
'End Namespace
#End Region 'not yet implemented.
#Region " NAMESPACE : RLE "
' Namespace RLE
'#Region " CLASS : Compressor "
' Public Class Compressor
' Private Overloads Function Equals(ByVal objA As Object, ByVal objB As Object) As Boolean
' End Function
' Private Overloads Function ReferenceEquals(ByVal objA As Object, ByVal objB As Object) As Boolean
' End Function
' Public ReadOnly Property Value() As Long
' Get
'  im lngValue As Long
' If Not (Me.fsInput Is Nothing) Then
' Try
' lngValue = Me.fsInput.Position
' Catch ex As Exception
' End Try
' End If
' Return lngValue
' End Get
' End Property
' Public ReadOnly Property Length() As Long
' Get
' Static lngLength As Long = -1
' If lngLength = -1 Then
'  im fileInfo As New IO.FileInfo(Me.Input)
' lngLength = fileInfo.Length
' End If
' Return lngLength
' End Get
' End Property
' Public Input As String
' Public Output As String
' Private fsInput As IO.FileStream
' Private fsOutput As IO.FileStream
' Public Sub New(ByVal Input As String, ByVal Output As String)
' Me.Input = Input
' Me.Output = Output
' End Sub
' Public Sub Compress()
' Me.fsInput = New IO.FileStream(Me.Input, IO.FileMode.Open)
' Me.fsOutput = New IO.FileStream(Me.Output, IO.FileMode.Create)
' 'read byte from file.
' 'if byte is last read byte then
' ' add count to last rleRepetition. (if its the right one,else create it)
' 'Else
' ' record the location at which the new byte was detected.
' ' write byte to output
' 'End If
'  im rleRepetitions() As RLErepetition
'  im lastReadByte As Integer = -2
'  im readByte As Integer
'  im differentCharacterPosition As Long
'  im repetitiveCharacterCount As Integer
'  o Until readByte = -1
' readByte = Me.fsInput.ReadByte
' If (readByte = -1) Then Exit Do
' If (readByte = lastReadByte) Then
' 'add count to last rleRepetition
' 'byte is the same, add count.
' repetitiveCharacterCount += 1
' Else
' 'byte is different. add last repetition to rleRepetitions(), if count is bigger than 0.
' If Not repetitiveCharacterCount = 0 Then
'  im bytes() As Byte = System.Text.Encoding.Default.GetBytes("*" & repetitiveCharacterCount.ToString & "*"
' Me.fsOutput.Write(bytes, 0, bytes.Length)
' End If
' differentCharacterPosition = Me.fsInput.Position
' repetitiveCharacterCount = 0
' Me.fsOutput.WriteByte(CByte(readByte))
' End If
' lastReadByte = readByte
' Loop
' Me.fsInput.Close()
' Me.fsOutput.Close()
' End Sub
' End Class
'#End Region
'#Region " CLASS : Decompressor "
' Public Class Decompressor
' Private Overloads Function Equals(ByVal objA As Object, ByVal objB As Object) As Boolean
' End Function
' Private Overloads Function ReferenceEquals(ByVal objA As Object, ByVal objB As Object) As Boolean
' End Function
' Public ReadOnly Property Value() As Long
' Get
'  im lngValue As Long
' If Not (Me.fsInput Is Nothing) Then
' Try
' lngValue = Me.fsInput.Position
' Catch ex As Exception
' End Try
' End If
' Return lngValue
' End Get
' End Property
' Public ReadOnly Property Length() As Long
' Get
' Static lngLength As Long = -1
' If lngLength = -1 Then
'  im fileInfo As New IO.FileInfo(Me.Input)
' lngLength = fileInfo.Length
' End If
' Return lngLength
' End Get
' End Property
' Public Input As String
' Public Output As String
' Private fsInput As IO.FileStream
' Private fsOutput As IO.FileStream
' Public Sub New(ByVal Input As String, ByVal Output As String)
' Me.Input = Input
' Me.Output = Output
' End Sub
' Public Sub Decompress()
' End Sub
' End Class
'#End Region
' Friend Class RLErepetition
' Public Location As Long
' Public Length As Long
' End Class
' End Namespace
#End Region 'just testing. not a very good compression method.
End Namespace
Номер ответа: 2
Автор ответа:
HACKER
Разработчик Offline Client
Вопросов: 236
Ответов: 8362
Профиль | | #2
Добавлено: 05.01.07 18:58
Ну полно всего готового, но интереснее самому осовить того же халфмана или lzw Вообщем гугли...
Номер ответа: 3
Автор ответа:
EROS
Вопросов: 58
Ответов: 4255
Профиль | | #3
Добавлено: 05.01.07 21:03
ага, изобрести так сказать новый велосипед?
Номер ответа: 4
Автор ответа:
Sacred Phoenix
ICQ: 304238252
Вопросов: 52
Ответов: 927
Профиль | | #4
Добавлено: 05.01.07 21:31
Номер ответа: 5
Автор ответа:
EROS
Вопросов: 58
Ответов: 4255
Профиль | | #5
Добавлено: 05.01.07 21:35
я вот вообще непонимаю смысла этих действий.. Возникла потребность в написании архиватора!! С чего это вдруг она возникла?? Чем туева хуча обычных не устраивает??
Номер ответа: 6
Автор ответа:
Artyom
Разработчик
Вопросов: 130
Ответов: 6602
Профиль | | #6
Добавлено: 05.01.07 21:43
This is how things work in Information Technology...
http://rsdn.ru/Forum/Info.aspx?name=FAQ.HUMOUR.russian_programmer
Номер ответа: 7
Автор ответа:
-АлександР-
Вопросов: 55
Ответов: 1008
Web-сайт:
Профиль | | #7
Добавлено: 05.01.07 21:45
Если готовый Билл Гейтс есть
Номер ответа: 8
Автор ответа:
W[4Fh]LF
Вопросов: 0
Ответов: 187
Web-сайт:
Профиль | | #8
Добавлено: 06.01.07 09:43
Писал хаффмана. Написал процентов 60%. Построение дерева, прохождение по нему уже было готово. Но очень нехватало битовых операций в ВБ, приодилось много гемориться. Короче не помню почему я забил, но до конца меня нехватило
Номер ответа: 9
Автор ответа:
HACKER
Разработчик Offline Client
Вопросов: 236
Ответов: 8362
Профиль | | #9
Добавлено: 07.01.07 01:49
Не ну а в учебных целей, для разминки моза и пальцев? Или так только я развликаюсь?
Номер ответа: 10
Автор ответа:
Sharp
Лидер форума
ICQ: 216865379
Вопросов: 106
Ответов: 9979
Web-сайт:
Профиль | | #10
Добавлено: 07.01.07 10:00
Нее, еще я так развлекаюсь
Номер ответа: 11
Автор ответа:
EROS
Вопросов: 58
Ответов: 4255
Профиль | | #11
Добавлено: 07.01.07 12:32
Я вот либо с GDI либо с MultiThreading разминаюсь в последнее время.. ))
Номер ответа: 12
Автор ответа:
-АлександР-
Вопросов: 55
Ответов: 1008
Web-сайт:
Профиль | | #12
Добавлено: 07.01.07 14:52
Шахматам замена ИМХО...
Номер ответа: 13
Автор ответа:
HACKER
Разработчик Offline Client
Вопросов: 236
Ответов: 8362
Профиль | | #13
Добавлено: 07.01.07 17:11
Я вот тоже в шахматы играл с компом, самого сильного противника поставил, 15 мин целых колбасил его, но таки я победил, таки нашёл нужные значения артманей, которые нужно заморозить Так что да, шахматы тоже норм...
MultiThreading тоже полезно... это вам дотнетчикам там весело, а мы LOL'a никак на пример не разведём ))
Номер ответа: 14
Автор ответа:
EROS
Вопросов: 58
Ответов: 4255
Профиль | | #14
Добавлено: 07.01.07 19:12
Я бы не сказал, что весело.. При более детальном изучении начинаешь понимать насколько это тяжелая и обширная тема. И легкость, на самом деле, только кажущаяся.. Создать поток - это просто.. Хорошо,если он один(фоновый).. А если их 2 и больше?? Вот тут то жопа и начинается.. когда начинаешь осознавать что значит синхронизировать потоки, и обеспечить безопасный доступ к глобальным переменным из разных потоков.. В 6-ке этих проблем с многопоточностью не было, потому как там используется совсем другая модель многопоточности.. упрощенная.. (и достаточно сложная в реализации). А тут дали возможностей на порядок больше, но и проблем прибавилось на столько же... Так что не все так безоблачно, как кажется...
Номер ответа: 15
Автор ответа:
Artyom
Разработчик
Вопросов: 130
Ответов: 6602
Профиль | | #15
Добавлено: 07.01.07 19:21
За несколько месяцев до презентации .NET я принимал участие в конференции VBits. Я спросил свою аудиторию, состоявшую из довольно опытных программистов Visual Basic, хотят ли они видеть свободную Многопоточность в следующей версии Visual Basic. Практически все подняли руки. Затем я спросил, кто из присутствующих знает, на что идет. На этот раз руки подняли всего несколько человек, и на их лицах были понимающие улыбки.
Д. Эпплман. Переход на VB .NET. Стратегии, концепции, код. Стр 117