Tại sao các biến không được khai báo trong phiên bản thử dùng trên phạm vi trong phạm vi bắt trong phạm vi bắt được


139

Trong C # và trong Java (và có thể cả các ngôn ngữ khác), các biến được khai báo trong khối "thử" không nằm trong phạm vi trong các khối "bắt" hoặc "cuối cùng" tương ứng. Ví dụ: đoạn mã sau không biên dịch:

try {
  String s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

Trong mã này, một lỗi thời gian biên dịch xảy ra trên tham chiếu đến s trong khối bắt, bởi vì s chỉ nằm trong phạm vi trong khối thử. (Trong Java, lỗi biên dịch là "không thể giải quyết được"; trong C #, đó là "Tên 's' không tồn tại trong ngữ cảnh hiện tại".)

Giải pháp chung cho vấn đề này dường như là thay vì khai báo các biến ngay trước khối thử, thay vì trong khối thử:

String s;
try {
  s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

Tuy nhiên, ít nhất với tôi, (1) điều này cảm thấy giống như một giải pháp cồng kềnh và (2) nó dẫn đến các biến có phạm vi lớn hơn so với lập trình viên dự định (toàn bộ phần còn lại của phương thức, thay vì chỉ trong bối cảnh của thử-bắt-cuối cùng).

Câu hỏi của tôi là, những lý do / là lý do đằng sau quyết định thiết kế ngôn ngữ này (bằng Java, bằng C # và / hoặc trong bất kỳ ngôn ngữ áp dụng nào khác)?

Câu trả lời:


171

Hai điều:

  1. Nói chung, Java chỉ có 2 cấp độ phạm vi: toàn cầu và chức năng. Nhưng, thử / bắt là một ngoại lệ (không có ý định chơi chữ). Khi một ngoại lệ được ném và đối tượng ngoại lệ nhận một biến được gán cho nó, biến đối tượng đó chỉ khả dụng trong phần "bắt" và bị hủy ngay khi bắt xong.

  2. (và quan trọng hơn). Bạn không thể biết nơi nào trong khối thử ngoại lệ được ném. Nó có thể là trước khi biến của bạn được khai báo. Do đó, không thể nói biến nào sẽ có sẵn cho mệnh đề Catch / cuối cùng. Hãy xem xét trường hợp sau đây, trong đó phạm vi là như bạn đề xuất:

    
    try
    {
        throw new ArgumentException("some operation that throws an exception");
        string s = "blah";
    }
    catch (e as ArgumentException)
    {  
        Console.Out.WriteLine(s);
    }

Đây rõ ràng là một vấn đề - khi bạn đạt đến trình xử lý ngoại lệ, s sẽ không được khai báo. Cho rằng các sản phẩm khai thác có nghĩa là để xử lý các trường hợp đặc biệt và cuối cùng phải thực thi, an toàn và tuyên bố đây là một vấn đề tại thời gian biên dịch tốt hơn nhiều so với lúc chạy.


55

Làm thế nào bạn có thể chắc chắn, rằng bạn đã đạt đến phần khai báo trong khối bắt của bạn? Điều gì xảy ra nếu việc khởi tạo ném ngoại lệ?


6
Huh? Khai báo biến không ném ngoại lệ.
Joshua

6
Đồng ý, đó là sự khởi tạo có thể ném ngoại lệ.
Burkhard

19

Theo truyền thống, trong các ngôn ngữ kiểu C, những gì xảy ra bên trong dấu ngoặc nhọn vẫn nằm trong dấu ngoặc nhọn. Tôi nghĩ rằng việc có thời gian tồn tại của một biến số trải dài trên các phạm vi như thế sẽ không trực quan với hầu hết các lập trình viên. Bạn có thể đạt được những gì bạn muốn bằng cách đặt các khối thử / bắt / cuối cùng bên trong một mức niềng răng khác. ví dụ

... code ...
{
    string s = "test";
    try
    {
        // more code
    }
    catch(...)
    {
        Console.Out.WriteLine(s);
    }
}

EDIT: Tôi đoán mọi quy tắc không có một ngoại lệ. Sau đây là C ++ hợp lệ:

int f() { return 0; }

void main() 
{
    int y = 0;

    if (int x = f())
    {
        cout << x;
    }
    else
    {
        cout << x;
    }
}

Phạm vi của x là mệnh đề có điều kiện, mệnh đề then và mệnh đề khác.


10

Mọi người khác đã đưa ra những điều cơ bản - những gì xảy ra trong một khối vẫn ở trong một khối. Nhưng trong trường hợp của .NET, có thể hữu ích để kiểm tra xem trình biên dịch nghĩ gì đang xảy ra. Lấy ví dụ, mã thử / bắt sau đây (lưu ý rằng StreamReader được khai báo, chính xác, bên ngoài các khối):

static void TryCatchFinally()
{
    StreamReader sr = null;
    try
    {
        sr = new StreamReader(path);
        Console.WriteLine(sr.ReadToEnd());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        if (sr != null)
        {
            sr.Close();
        }
    }
}

Điều này sẽ biên dịch thành một cái gì đó tương tự như sau trong MSIL:

.method private hidebysig static void  TryCatchFinallyDispose() cil managed
{
  // Code size       53 (0x35)    
  .maxstack  2    
  .locals init ([0] class [mscorlib]System.IO.StreamReader sr,    
           [1] class [mscorlib]System.Exception ex)    
  IL_0000:  ldnull    
  IL_0001:  stloc.0    
  .try    
  {    
    .try    
    {    
      IL_0002:  ldsfld     string UsingTest.Class1::path    
      IL_0007:  newobj     instance void [mscorlib]System.IO.StreamReader::.ctor(string)    
      IL_000c:  stloc.0    
      IL_000d:  ldloc.0    
      IL_000e:  callvirt   instance string [mscorlib]System.IO.TextReader::ReadToEnd()
      IL_0013:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0018:  leave.s    IL_0028
    }  // end .try
    catch [mscorlib]System.Exception 
    {
      IL_001a:  stloc.1
      IL_001b:  ldloc.1    
      IL_001c:  callvirt   instance string [mscorlib]System.Exception::ToString()    
      IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0026:  leave.s    IL_0028    
    }  // end handler    
    IL_0028:  leave.s    IL_0034    
  }  // end .try    
  finally    
  {    
    IL_002a:  ldloc.0    
    IL_002b:  brfalse.s  IL_0033    
    IL_002d:  ldloc.0    
    IL_002e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()    
    IL_0033:  endfinally    
  }  // end handler    
  IL_0034:  ret    
} // end of method Class1::TryCatchFinallyDispose

Chúng ta thấy gì? MSIL tôn trọng các khối - về bản chất chúng là một phần của mã cơ bản được tạo khi bạn biên dịch C # của mình. Phạm vi không chỉ được thiết lập cứng trong thông số C #, nó cũng nằm trong thông số CLR và CLS.

Phạm vi bảo vệ bạn, nhưng đôi khi bạn phải làm việc xung quanh nó. Theo thời gian, bạn đã quen với nó, và nó bắt đầu cảm thấy tự nhiên. Giống như mọi người khác đã nói, những gì xảy ra trong một khối vẫn nằm trong khối đó. Bạn muốn chia sẻ điều gì? Bạn phải đi ra ngoài khối ...


8

Trong C ++ ở bất kỳ tỷ lệ nào, phạm vi của một biến tự động bị giới hạn bởi các dấu ngoặc nhọn bao quanh nó. Tại sao bất cứ ai cũng mong đợi điều này sẽ khác đi bằng cách tìm ra một từ khóa thử bên ngoài các dấu ngoặc nhọn?


1
Đã đồng ý; "}" có nghĩa là kết thúc phạm vi. Tuy nhiên, thử-bắt-cuối cùng là bất thường ở chỗ sau một khối thử, bạn phải có một khối bắt và / hoặc cuối cùng; do đó, một ngoại lệ đối với quy tắc thông thường trong đó phạm vi của khối thử được thực hiện trong lần bắt liên kết / cuối cùng có vẻ chấp nhận được?
Jon Schneider

7

Giống như ravenspoint đã chỉ ra, mọi người đều mong đợi các biến là cục bộ của khối mà họ được xác định. tryGiới thiệu một khối và cũng vậy catch.

Nếu bạn muốn các biến cục bộ cho cả hai trycatch, hãy thử đặt cả hai trong một khối:

// here is some code
{
    string s;
    try
    {

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}

5

Câu trả lời đơn giản là C và hầu hết các ngôn ngữ đã kế thừa cú pháp của nó đều nằm trong phạm vi khối. Điều đó có nghĩa là nếu một biến được định nghĩa trong một khối, tức là bên trong {}, đó là phạm vi của nó.

Nhân tiện, ngoại lệ là JavaScript, có cú pháp tương tự, nhưng có phạm vi chức năng. Trong JavaScript, một biến được khai báo trong khối thử nằm trong phạm vi trong khối bắt và ở mọi nơi khác trong hàm chứa của nó.


4

@burkhard có câu hỏi là tại sao trả lời đúng, nhưng như một lưu ý tôi muốn thêm vào, trong khi ví dụ về giải pháp được đề xuất của bạn là 99.9999 +% thời gian, nó không phải là thực hành tốt, sẽ an toàn hơn rất nhiều khi kiểm tra null trước khi sử dụng một cái gì đó khởi tạo trong khối thử hoặc khởi tạo biến thành thứ gì đó thay vì chỉ khai báo nó trước khối thử. Ví dụ:

string s = String.Empty;
try
{
    //do work
}
catch
{
   //safely access s
   Console.WriteLine(s);
}

Hoặc là:

string s;
try
{
    //do work
}
catch
{
   if (!String.IsNullOrEmpty(s))
   {
       //safely access s
       Console.WriteLine(s);
   }
}

Điều này sẽ cung cấp khả năng mở rộng trong cách giải quyết, do đó ngay cả khi những gì bạn đang làm trong khối thử phức tạp hơn việc gán một chuỗi, bạn sẽ có thể truy cập dữ liệu một cách an toàn từ khối bắt của bạn.


4

Theo phần có tiêu đề "Cách ném và bắt ngoại lệ" trong Bài 2 của Bộ công cụ đào tạo tự luyện MCTS (Bài kiểm tra 70-536): Tổ chức phát triển ứng dụng Microsoft® .NET Framework 2.0 , lý do là ngoại lệ có thể xảy ra trước khi khai báo biến trong khối thử (như những người khác đã lưu ý).

Trích dẫn từ trang 25:

"Lưu ý rằng khai báo StreamReader đã được di chuyển bên ngoài khối Thử trong ví dụ trước. Điều này là cần thiết vì khối Cuối cùng không thể truy cập các biến được khai báo trong khối Thử. Điều này hợp lý vì tùy thuộc vào nơi xảy ra ngoại lệ, khai báo biến trong Khối thử có thể chưa được thực thi . "


4

Câu trả lời, như mọi người đã chỉ ra, là khá nhiều "đó là cách các khối được xác định".

Có một số đề xuất để làm cho mã đẹp hơn. Xem ARM

 try (FileReader in = makeReader(), FileWriter out = makeWriter()) {
       // code using in and out
 } catch(IOException e) {
       // ...
 }

Đóng cửa được cho là để giải quyết điều này là tốt.

with(FileReader in : makeReader()) with(FileWriter out : makeWriter()) {
    // code using in and out
}

CẬP NHẬT: ARM được triển khai trong Java 7. http://doad.java.net/jdk7/docs/technotes/guides/lingu/try-with-resource.html


2

Bạn giải pháp là chính xác những gì bạn nên làm. Bạn không thể chắc chắn rằng tuyên bố của bạn thậm chí đã đạt được trong khối thử, điều này sẽ dẫn đến một ngoại lệ khác trong khối bắt.

Nó chỉ đơn giản là phải làm việc như phạm vi riêng biệt.

try
    dim i as integer = 10 / 0 ''// Throw an exception
    dim s as string = "hi"
catch (e)
    console.writeln(s) ''// Would throw another exception, if this was allowed to compile
end try

2

Các biến là cấp độ khối và được giới hạn trong khối Thử hoặc Bắt đó. Tương tự như việc xác định một biến trong một câu lệnh if. Hãy nghĩ về tình huống này.

try {    
    fileOpen("no real file Name");    
    String s = "GO TROJANS"; 
} catch (Exception) {   
    print(s); 
}

Chuỗi sẽ không bao giờ được khai báo, vì vậy nó không thể phụ thuộc vào.


2

Bởi vì khối thử và khối bắt là 2 khối khác nhau.

Trong đoạn mã sau, bạn có mong đợi s được xác định trong khối A sẽ hiển thị trong khối B không?

{ // block A
  string s = "dude";
}

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}

2

Mặc dù trong ví dụ của bạn, điều kỳ lạ là nó không hoạt động, hãy lấy cái tương tự này:

    try
    {
         //Code 1
         String s = "1|2";
         //Code 2
    }
    catch
    {
         Console.WriteLine(s.Split('|')[1]);
    }

Điều này sẽ khiến cho sản phẩm khai thác ném ngoại lệ tham chiếu null nếu Mã 1 bị hỏng. Bây giờ trong khi ngữ nghĩa của thử / bắt được hiểu khá rõ, đây sẽ là một trường hợp góc khó chịu, vì s được định nghĩa với một giá trị ban đầu, vì vậy về mặt lý thuyết, nó sẽ không bao giờ là null, nhưng theo ngữ nghĩa được chia sẻ, thì nó sẽ như vậy.

Một lần nữa, về lý thuyết có thể được sửa chữa bằng cách chỉ cho phép các định nghĩa riêng biệt (String s; s = "1|2"; ) hoặc một số điều kiện khác, nhưng nói chung là dễ dàng hơn để nói không.

Ngoài ra, nó cho phép các ngữ nghĩa của phạm vi được xác định trên toàn cầu mà không có ngoại lệ, cụ thể, các địa phương tồn tại miễn là {} chúng được định nghĩa trong mọi trường hợp. Điểm nhỏ, nhưng một điểm.

Cuối cùng, để làm những gì bạn muốn, bạn có thể thêm một bộ dấu ngoặc quanh thử bắt. Cung cấp cho bạn phạm vi bạn muốn, mặc dù nó có chi phí dễ đọc, nhưng không quá nhiều.

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}

1

Trong ví dụ cụ thể bạn đã đưa ra, việc khởi tạo không thể đưa ra một ngoại lệ. Vì vậy, bạn nghĩ rằng có thể phạm vi của nó có thể được mở rộng.

Nhưng nói chung, các biểu thức khởi tạo có thể ném ngoại lệ. Sẽ không có nghĩa gì khi một biến có trình khởi tạo của nó đưa ra một ngoại lệ (hoặc được khai báo sau một biến khác xảy ra) trong phạm vi bắt / cuối cùng.

Ngoài ra, khả năng đọc mã sẽ bị. Quy tắc trong C (và các ngôn ngữ tuân theo nó, bao gồm C ++, Java và C #) rất đơn giản: phạm vi biến theo các khối.

Nếu bạn muốn một biến nằm trong phạm vi cho thử / bắt / cuối cùng nhưng không ở đâu khác, thì hãy bọc toàn bộ trong một bộ dấu ngoặc khác (một khối trần) và khai báo biến trước khi thử.


1

Một phần lý do chúng không nằm trong cùng phạm vi là vì tại bất kỳ điểm nào của khối thử, bạn có thể đã ném ngoại lệ. Nếu chúng ở trong cùng một phạm vi, đó là một thảm họa trong chờ đợi, bởi vì tùy thuộc vào nơi ngoại lệ được ném, nó có thể còn mơ hồ hơn.

Ít nhất là khi nó được khai báo bên ngoài khối thử, bạn biết chắc chắn biến tối thiểu có thể là gì khi ném ngoại lệ; Giá trị của biến trước khối thử.


1

Khi bạn khai báo một biến cục bộ, nó được đặt trên ngăn xếp (đối với một số loại, toàn bộ giá trị của đối tượng sẽ nằm trên ngăn xếp, đối với các loại khác chỉ có một tham chiếu sẽ nằm trên ngăn xếp). Khi có một ngoại lệ bên trong khối thử, các biến cục bộ trong khối sẽ được giải phóng, điều đó có nghĩa là ngăn xếp "không giải quyết" trở lại trạng thái ở đầu khối thử. Đây là do thiết kế. Đó là cách thử / bắt có thể sao lưu tất cả các lệnh gọi hàm trong khối và đưa hệ thống của bạn trở lại trạng thái chức năng. Không có cơ chế này, bạn không bao giờ có thể chắc chắn về trạng thái của bất cứ điều gì khi xảy ra ngoại lệ.

Có mã xử lý lỗi của bạn dựa vào các biến được khai báo bên ngoài có giá trị của chúng thay đổi bên trong khối thử có vẻ như là thiết kế xấu đối với tôi. Những gì bạn đang làm về cơ bản là rò rỉ tài nguyên một cách có chủ đích để có được thông tin (trong trường hợp cụ thể này không tệ lắm vì bạn chỉ bị rò rỉ thông tin, nhưng hãy tưởng tượng nếu đó là một tài nguyên khác? Tương lai). Tôi sẽ đề nghị chia nhỏ các khối thử của bạn thành các phần nhỏ hơn nếu bạn yêu cầu mức độ chi tiết cao hơn trong xử lý lỗi.


1

Khi bạn có một thử bắt, phần lớn bạn nên biết rằng các lỗi mà nó có thể gây ra. Các lớp ngoại lệ Theese Normaly nói mọi thứ bạn cần về ngoại lệ. Nếu không, bạn nên tạo các lớp ngoại lệ của riêng bạn và chuyển thông tin đó cùng. Bằng cách đó, bạn sẽ không bao giờ cần lấy các biến từ bên trong khối thử, vì Ngoại lệ là tự giải thích. Vì vậy, nếu bạn cần thực hiện nhiều điều này, hãy nghĩ về thiết kế của bạn và thử nghĩ xem có cách nào khác không, bạn có thể dự đoán các ngoại lệ đang đến hoặc sử dụng thông tin đến từ các ngoại lệ, và sau đó có thể suy nghĩ lại ngoại lệ với nhiều thông tin hơn.


1

Như đã được chỉ ra bởi những người dùng khác, dấu ngoặc nhọn xác định phạm vi trong hầu hết mọi ngôn ngữ kiểu C mà tôi biết.

Nếu đó là một biến đơn giản, thì tại sao bạn quan tâm nó sẽ nằm trong phạm vi bao lâu? Đó không phải là một vấn đề lớn.

trong C #, nếu đó là một biến phức tạp, bạn sẽ muốn triển khai IDis Dùng một lần. Sau đó, bạn có thể sử dụng thử / bắt / cuối cùng và gọi obj.Dispose () trong khối cuối cùng. Hoặc bạn có thể sử dụng từ khóa sử dụng, từ khóa này sẽ tự động gọi Dispose ở cuối phần mã.


1

Trong Python, chúng có thể nhìn thấy trong các khối bắt / cuối cùng nếu dòng khai báo chúng không ném.


1

Điều gì xảy ra nếu ngoại lệ được ném trong một số mã nằm trên khai báo của biến. Điều đó có nghĩa, bản thân tuyên bố đã không xảy ra trong trường hợp này.

try {

       //doSomeWork // Exception is thrown in this line. 
       String s;
       //doRestOfTheWork

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}

1

Các C # Spec (15,2) khẳng định "Phạm vi của một biến địa phương hoặc liên tục tuyên bố trong một khối ist khối."

(trong ví dụ đầu tiên của bạn, khối thử là khối nơi "s" được khai báo)


0

Tôi nghĩ rằng bởi vì một cái gì đó trong khối thử đã kích hoạt ngoại lệ, nội dung không gian tên của nó không thể tin cậy được - tức là việc tham chiếu Chuỗi 'trong khối bắt có thể gây ra ngoại lệ khác.


0

Chà, nếu nó không đưa ra một lỗi biên dịch và bạn có thể khai báo nó cho phần còn lại của phương thức, thì sẽ không có cách nào để chỉ khai báo nó trong phạm vi thử. Điều đó buộc bạn phải rõ ràng về nơi mà biến được cho là tồn tại và không đưa ra các giả định.


0

Nếu chúng ta bỏ qua vấn đề chặn phạm vi trong giây lát, trình biên dịch sẽ phải làm việc vất vả hơn rất nhiều trong tình huống không được xác định rõ. Mặc dù điều này là không thể, nhưng lỗi phạm vi cũng buộc bạn, tác giả của mã, nhận ra hàm ý của mã bạn viết (rằng chuỗi s có thể là null trong khối bắt). Nếu mã của bạn là hợp pháp, trong trường hợp ngoại lệ OutOfMemory, s thậm chí không được đảm bảo để được cấp một khe nhớ:

// won't compile!
try
{
    VeryLargeArray v = new VeryLargeArray(TOO_BIG_CONSTANT); // throws OutOfMemoryException
    string s = "Help";
}
catch
{
    Console.WriteLine(s); // whoops!
}

CLR (và do đó trình biên dịch) cũng buộc bạn phải khởi tạo các biến trước khi chúng được sử dụng. Trong khối bắt được trình bày, nó không thể đảm bảo điều này.

Vì vậy, chúng tôi kết thúc với trình biên dịch phải thực hiện rất nhiều công việc, trong thực tế không mang lại nhiều lợi ích và có thể sẽ khiến mọi người nhầm lẫn và khiến họ hỏi tại sao thử / bắt hoạt động khác nhau.

Ngoài tính nhất quán, bằng cách không cho phép bất cứ điều gì lạ mắt và tuân thủ ngữ nghĩa phạm vi đã được thiết lập được sử dụng trong toàn ngôn ngữ, trình biên dịch và CLR có thể đảm bảo lớn hơn về trạng thái của một biến trong khối bắt. Rằng nó tồn tại và đã được khởi tạo.

Lưu ý rằng các nhà thiết kế ngôn ngữ đã thực hiện tốt công việc với các cấu trúc khác như sử dụngkhóa trong đó vấn đề và phạm vi được xác định rõ, cho phép bạn viết mã rõ ràng hơn.

ví dụ: từ khóa sử dụng với các đối tượng IDis Dùng trong:

using(Writer writer = new Writer())
{
    writer.Write("Hello");
}

tương đương với:

Writer writer = new Writer();
try
{        
    writer.Write("Hello");
}
finally
{
    if( writer != null)
    {
        ((IDisposable)writer).Dispose();
    }
}

Nếu thử / bắt / cuối cùng của bạn là khó hiểu, hãy thử tái cấu trúc hoặc giới thiệu một lớp cảm ứng khác với một lớp trung gian gói gọn các ngữ nghĩa của những gì bạn đang cố gắng thực hiện. Không nhìn thấy mã thực, thật khó để cụ thể hơn.


0

Thay vì một biến cục bộ, một tài sản công có thể được khai báo; điều này cũng nên tránh một lỗi tiềm ẩn khác của một biến không được gán. chuỗi công khai S {get; bộ; }


-1

Nếu thao tác gán thất bại, câu lệnh bắt của bạn sẽ có một tham chiếu null trở lại biến không được gán.


2
Nó không được chỉ định. Nó thậm chí không null (không giống như các biến thể và biến tĩnh).
Tom Hawtin - tackline

-1

C # 3.0:

string html = new Func<string>(() =>
{
    string webpage;

    try
    {
        using(WebClient downloader = new WebClient())
        {
            webpage = downloader.DownloadString(url);
        }
    }
    catch(WebException)
    {
        Console.WriteLine("Download failed.");  
    }

    return webpage;
})();

WTF? Tại sao bỏ phiếu xuống? Đóng gói là không thể thiếu đối với OOP. Trông cũng đẹp đấy.
cốt lõi

2
Tôi không phải là downvote, nhưng những gì sai đang trả về một chuỗi chưa được khởi tạo.
Ben Voigt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.